From b71ad865dc2777ef7184d6e02be1db0ca91963ff Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Wed, 25 Feb 2026 11:39:43 +0000 Subject: [PATCH 1/4] feat: refresh private repository if updating --- .../new/github-private-repository.blade.php | 19 ++- templates/service-templates-latest.json | 57 +------- templates/service-templates.json | 57 +------- tests/Feature/GithubPrivateRepositoryTest.php | 126 ++++++++++++++++++ 4 files changed, 145 insertions(+), 114 deletions(-) create mode 100644 tests/Feature/GithubPrivateRepositoryTest.php diff --git a/resources/views/livewire/project/new/github-private-repository.blade.php b/resources/views/livewire/project/new/github-private-repository.blade.php index 129c508a9..27ef6a189 100644 --- a/resources/views/livewire/project/new/github-private-repository.blade.php +++ b/resources/views/livewire/project/new/github-private-repository.blade.php @@ -4,16 +4,27 @@ - @if ($repositories->count() > 0) + +
Deploy any public or private Git repositories through a GitHub App.
+ @if ($repositories->count() > 0) +
Change Repositories on GitHub - @endif -
-
Deploy any public or private Git repositories through a GitHub App.
+ + + + + + + + + + + @endif @if ($github_apps->count() !== 0)
@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 7638912fdc56e0ef92bfe13821ec6ee0ab07d548 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:50:19 +0200 Subject: [PATCH 2/4] fix(github): reset branch state when refreshing repositories Clear `branches` and `total_branches_count` in `loadRepositories` to avoid stale branch data after repo refreshes. Update the Livewire view to use the shared loading button pattern for refresh/load actions, and expand feature coverage for repository refresh behavior and refresh button visibility. --- .../Project/New/GithubPrivateRepository.php | 2 + .../new/github-private-repository.blade.php | 34 +++++------ tests/Feature/GithubPrivateRepositoryTest.php | 60 +++++++++++++++---- 3 files changed, 65 insertions(+), 31 deletions(-) diff --git a/app/Livewire/Project/New/GithubPrivateRepository.php b/app/Livewire/Project/New/GithubPrivateRepository.php index 6aa8db085..9d4acb9bb 100644 --- a/app/Livewire/Project/New/GithubPrivateRepository.php +++ b/app/Livewire/Project/New/GithubPrivateRepository.php @@ -99,6 +99,8 @@ public function updatedBuildPack() public function loadRepositories($github_app_id) { $this->repositories = collect(); + $this->branches = collect(); + $this->total_branches_count = 0; $this->page = 1; $this->selected_github_app_id = $github_app_id; $this->github_app = GithubApp::where('id', $github_app_id)->first(); diff --git a/resources/views/livewire/project/new/github-private-repository.blade.php b/resources/views/livewire/project/new/github-private-repository.blade.php index 27ef6a189..ec0d17506 100644 --- a/resources/views/livewire/project/new/github-private-repository.blade.php +++ b/resources/views/livewire/project/new/github-private-repository.blade.php @@ -4,27 +4,18 @@ + @if ($repositories->count() > 0) + + Refresh Repository List + + + Change Repositories on GitHub + + + @endif
Deploy any public or private Git repositories through a GitHub App.
- @if ($repositories->count() > 0) -
- - - Change Repositories on GitHub - - - - - - - - - - - - -
- @endif @if ($github_apps->count() !== 0)
@if ($current_step === 'github_apps') @@ -62,7 +53,10 @@ @endforeach
- Load Repository + + Load Repository + + @else
No repositories found. Check your GitHub App configuration.
diff --git a/tests/Feature/GithubPrivateRepositoryTest.php b/tests/Feature/GithubPrivateRepositoryTest.php index 19474caca..abc288519 100644 --- a/tests/Feature/GithubPrivateRepositoryTest.php +++ b/tests/Feature/GithubPrivateRepositoryTest.php @@ -31,7 +31,7 @@ 'team_id' => $this->team->id, ]); - $this->githubApp = GithubApp::create([ + $this->githubApp = GithubApp::forceCreate([ 'name' => 'Test GitHub App', 'api_url' => 'https://api.github.com', 'html_url' => 'https://github.com', @@ -86,27 +86,65 @@ function fakeGithubHttp(array $repositories): void ['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); + $callCount = 0; + 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*' => function () use (&$callCount, $initialRepos, $updatedRepos) { + $callCount++; + $repos = $callCount === 1 ? $initialRepos : $updatedRepos; + return Http::response([ + 'total_count' => count($repos), + 'repositories' => $repos, + ], 200); + }, + ]); + + $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 $component ->call('loadRepositories', $this->githubApp->id) ->assertSet('total_repositories_count', 3) ->assertSet('current_step', 'repository'); }); + test('loadRepositories resets branches when refreshing', function () { + $repos = [ + ['id' => 1, 'name' => 'alpha-repo', 'owner' => ['login' => 'testuser']], + ]; + + fakeGithubHttp($repos); + + $component = Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app']) + ->call('loadRepositories', $this->githubApp->id); + + // Manually set branches to simulate a previous branch load + $component->set('branches', collect([['name' => 'main'], ['name' => 'develop']])); + $component->set('total_branches_count', 2); + + // Refresh repositories should reset branches + fakeGithubHttp($repos); + + $component + ->call('loadRepositories', $this->githubApp->id) + ->assertSet('total_branches_count', 0) + ->assertSet('branches', collect()); + }); + test('refresh button is visible when repositories are loaded', function () { $repos = [ ['id' => 1, 'name' => 'alpha-repo', 'owner' => ['login' => 'testuser']], @@ -116,11 +154,11 @@ function fakeGithubHttp(array $repositories): void Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app']) ->call('loadRepositories', $this->githubApp->id) - ->assertSeeHtml('title="Refresh Repository List"'); + ->assertSee('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"'); + ->assertDontSee('Refresh Repository List'); }); }); From 1a603a10ed9502399c155b075a5ec0f93b16682d Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 31 Mar 2026 13:45:31 +0200 Subject: [PATCH 3/4] fix(models): replace forceFill/forceCreate with fill/create and add fillable guards Replace all uses of `forceFill`, `forceCreate`, and `forceFill` with their non-force equivalents across models, actions, controllers, and Livewire components. Add explicit `$fillable` arrays to all affected Eloquent models to enforce mass assignment protection. Add ModelFillableCreationTest and ModelFillableRegressionTest to verify that model creation respects fillable constraints and prevent regressions. --- app/Actions/Fortify/ResetUserPassword.php | 2 +- app/Actions/Fortify/UpdateUserPassword.php | 2 +- .../Fortify/UpdateUserProfileInformation.php | 4 +- app/Actions/Server/InstallDocker.php | 2 +- app/Console/Commands/Emails.php | 2 +- .../Controllers/Api/ProjectController.php | 2 +- .../Controllers/Api/ServicesController.php | 2 +- app/Http/Controllers/Webhook/Bitbucket.php | 4 +- app/Http/Controllers/Webhook/Gitea.php | 4 +- app/Http/Controllers/Webhook/Gitlab.php | 4 +- app/Jobs/ProcessGithubPullRequestWebhook.php | 4 +- app/Livewire/Boarding/Index.php | 2 +- app/Livewire/Destination/New/Docker.php | 4 +- app/Livewire/ForcePasswordReset.php | 2 +- app/Livewire/Project/AddEmpty.php | 2 +- app/Livewire/Project/Application/Previews.php | 6 +- app/Livewire/Project/CloneMe.php | 32 +- app/Livewire/Project/New/DockerCompose.php | 2 +- app/Livewire/Project/New/DockerImage.php | 2 +- app/Livewire/Project/New/EmptyProject.php | 2 +- .../Project/New/GithubPrivateRepository.php | 2 +- .../New/GithubPrivateRepositoryDeployKey.php | 2 +- .../Project/New/PublicGitRepository.php | 4 +- app/Livewire/Project/New/SimpleDockerfile.php | 2 +- app/Livewire/Project/Resource/Create.php | 2 +- .../Project/Shared/ResourceOperations.php | 26 +- app/Livewire/Project/Show.php | 2 +- app/Livewire/Server/Destinations.php | 4 +- app/Models/Application.php | 12 +- app/Models/ApplicationDeploymentQueue.php | 1 + app/Models/ApplicationPreview.php | 3 +- app/Models/ApplicationSetting.php | 1 + app/Models/CloudProviderToken.php | 1 + app/Models/Environment.php | 2 + app/Models/GithubApp.php | 2 + app/Models/Project.php | 6 +- app/Models/ProjectSetting.php | 4 +- app/Models/ScheduledDatabaseBackup.php | 2 + .../ScheduledDatabaseBackupExecution.php | 2 + app/Models/ScheduledTask.php | 4 + app/Models/ScheduledTaskExecution.php | 1 + app/Models/Server.php | 3 +- app/Models/ServerSetting.php | 1 + app/Models/Service.php | 5 + app/Models/ServiceApplication.php | 3 +- app/Models/ServiceDatabase.php | 3 +- app/Models/StandaloneClickhouse.php | 6 +- app/Models/StandaloneDocker.php | 1 + app/Models/StandaloneDragonfly.php | 6 +- app/Models/StandaloneKeydb.php | 6 +- app/Models/StandaloneMariadb.php | 6 +- app/Models/StandaloneMongodb.php | 6 +- app/Models/StandaloneMysql.php | 6 +- app/Models/StandalonePostgresql.php | 6 +- app/Models/StandaloneRedis.php | 6 +- app/Models/Subscription.php | 1 + app/Models/SwarmDocker.php | 1 + app/Models/Tag.php | 1 + app/Models/User.php | 5 +- bootstrap/helpers/applications.php | 16 +- bootstrap/helpers/parsers.php | 4 +- bootstrap/helpers/shared.php | 8 +- .../Feature/ApplicationHealthCheckApiTest.php | 2 +- tests/Feature/ApplicationRollbackTest.php | 2 +- .../Feature/ClonePersistentVolumeUuidTest.php | 8 +- tests/Feature/ComposePreviewFqdnTest.php | 6 +- .../DatabaseEnvironmentVariableApiTest.php | 2 +- .../DatabasePublicPortTimeoutApiTest.php | 8 +- .../Feature/DatabaseSslStatusRefreshTest.php | 4 +- tests/Feature/GetLogsCommandInjectionTest.php | 4 +- tests/Feature/GithubPrivateRepositoryTest.php | 2 +- ...nternalModelCreationMassAssignmentTest.php | 4 +- tests/Feature/ModelFillableCreationTest.php | 1114 +++++++++++++++++ tests/Feature/ServiceDatabaseTeamTest.php | 16 +- tests/Feature/StorageApiTest.php | 2 +- tests/Unit/GitRefValidationTest.php | 18 +- tests/Unit/ModelFillableRegressionTest.php | 76 ++ tests/Unit/ServiceParserImageUpdateTest.php | 8 +- tests/v4/Browser/DashboardTest.php | 6 +- 79 files changed, 1411 insertions(+), 142 deletions(-) create mode 100644 tests/Feature/ModelFillableCreationTest.php create mode 100644 tests/Unit/ModelFillableRegressionTest.php diff --git a/app/Actions/Fortify/ResetUserPassword.php b/app/Actions/Fortify/ResetUserPassword.php index 158996c90..5baa8b7ed 100644 --- a/app/Actions/Fortify/ResetUserPassword.php +++ b/app/Actions/Fortify/ResetUserPassword.php @@ -21,7 +21,7 @@ public function reset(User $user, array $input): void 'password' => ['required', Password::defaults(), 'confirmed'], ])->validate(); - $user->forceFill([ + $user->fill([ 'password' => Hash::make($input['password']), ])->save(); $user->deleteAllSessions(); diff --git a/app/Actions/Fortify/UpdateUserPassword.php b/app/Actions/Fortify/UpdateUserPassword.php index 0c51ec56d..320eede0b 100644 --- a/app/Actions/Fortify/UpdateUserPassword.php +++ b/app/Actions/Fortify/UpdateUserPassword.php @@ -24,7 +24,7 @@ public function update(User $user, array $input): void 'current_password.current_password' => __('The provided password does not match your current password.'), ])->validateWithBag('updatePassword'); - $user->forceFill([ + $user->fill([ 'password' => Hash::make($input['password']), ])->save(); } diff --git a/app/Actions/Fortify/UpdateUserProfileInformation.php b/app/Actions/Fortify/UpdateUserProfileInformation.php index c8bfd930a..76c6c0736 100644 --- a/app/Actions/Fortify/UpdateUserProfileInformation.php +++ b/app/Actions/Fortify/UpdateUserProfileInformation.php @@ -35,7 +35,7 @@ public function update(User $user, array $input): void ) { $this->updateVerifiedUser($user, $input); } else { - $user->forceFill([ + $user->fill([ 'name' => $input['name'], 'email' => $input['email'], ])->save(); @@ -49,7 +49,7 @@ public function update(User $user, array $input): void */ protected function updateVerifiedUser(User $user, array $input): void { - $user->forceFill([ + $user->fill([ 'name' => $input['name'], 'email' => $input['email'], 'email_verified_at' => null, diff --git a/app/Actions/Server/InstallDocker.php b/app/Actions/Server/InstallDocker.php index 8bb85c7fc..2e08ec6ad 100644 --- a/app/Actions/Server/InstallDocker.php +++ b/app/Actions/Server/InstallDocker.php @@ -49,7 +49,7 @@ public function handle(Server $server) }'); $found = StandaloneDocker::where('server_id', $server->id); if ($found->count() == 0 && $server->id) { - StandaloneDocker::forceCreate([ + StandaloneDocker::create([ 'name' => 'coolify', 'network' => 'coolify', 'server_id' => $server->id, diff --git a/app/Console/Commands/Emails.php b/app/Console/Commands/Emails.php index 462155142..43ba06804 100644 --- a/app/Console/Commands/Emails.php +++ b/app/Console/Commands/Emails.php @@ -136,7 +136,7 @@ public function handle() $application = Application::all()->first(); $preview = ApplicationPreview::all()->first(); if (! $preview) { - $preview = ApplicationPreview::forceCreate([ + $preview = ApplicationPreview::create([ 'application_id' => $application->id, 'pull_request_id' => 1, 'pull_request_html_url' => 'http://example.com', diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index c8638be0d..ec2e300ff 100644 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -258,7 +258,7 @@ public function create_project(Request $request) ], 422); } - $project = Project::forceCreate([ + $project = Project::create([ 'name' => $request->name, 'description' => $request->description, 'team_id' => $teamId, diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 6a742fe1b..fbf4b9e56 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -432,7 +432,7 @@ public function create_service(Request $request) if (in_array($oneClickServiceName, NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK)) { data_set($servicePayload, 'connect_to_docker_network', true); } - $service = Service::forceCreate($servicePayload); + $service = Service::create($servicePayload); $service->name = $request->name ?? "$oneClickServiceName-".$service->uuid; $service->description = $request->description; if ($request->has('is_container_label_escape_enabled')) { diff --git a/app/Http/Controllers/Webhook/Bitbucket.php b/app/Http/Controllers/Webhook/Bitbucket.php index e59bc6ead..183186711 100644 --- a/app/Http/Controllers/Webhook/Bitbucket.php +++ b/app/Http/Controllers/Webhook/Bitbucket.php @@ -119,7 +119,7 @@ public function manual(Request $request) $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); if (! $found) { if ($application->build_pack === 'dockercompose') { - $pr_app = ApplicationPreview::forceCreate([ + $pr_app = ApplicationPreview::create([ 'git_type' => 'bitbucket', 'application_id' => $application->id, 'pull_request_id' => $pull_request_id, @@ -128,7 +128,7 @@ public function manual(Request $request) ]); $pr_app->generate_preview_fqdn_compose(); } else { - $pr_app = ApplicationPreview::forceCreate([ + $pr_app = ApplicationPreview::create([ 'git_type' => 'bitbucket', 'application_id' => $application->id, 'pull_request_id' => $pull_request_id, diff --git a/app/Http/Controllers/Webhook/Gitea.php b/app/Http/Controllers/Webhook/Gitea.php index 6ba4b33cf..a9d65eae6 100644 --- a/app/Http/Controllers/Webhook/Gitea.php +++ b/app/Http/Controllers/Webhook/Gitea.php @@ -144,7 +144,7 @@ public function manual(Request $request) $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); if (! $found) { if ($application->build_pack === 'dockercompose') { - $pr_app = ApplicationPreview::forceCreate([ + $pr_app = ApplicationPreview::create([ 'git_type' => 'gitea', 'application_id' => $application->id, 'pull_request_id' => $pull_request_id, @@ -153,7 +153,7 @@ public function manual(Request $request) ]); $pr_app->generate_preview_fqdn_compose(); } else { - $pr_app = ApplicationPreview::forceCreate([ + $pr_app = ApplicationPreview::create([ 'git_type' => 'gitea', 'application_id' => $application->id, 'pull_request_id' => $pull_request_id, diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index fe4f17d9e..08e5d7162 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -177,7 +177,7 @@ public function manual(Request $request) $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); if (! $found) { if ($application->build_pack === 'dockercompose') { - $pr_app = ApplicationPreview::forceCreate([ + $pr_app = ApplicationPreview::create([ 'git_type' => 'gitlab', 'application_id' => $application->id, 'pull_request_id' => $pull_request_id, @@ -186,7 +186,7 @@ public function manual(Request $request) ]); $pr_app->generate_preview_fqdn_compose(); } else { - $pr_app = ApplicationPreview::forceCreate([ + $pr_app = ApplicationPreview::create([ 'git_type' => 'gitlab', 'application_id' => $application->id, 'pull_request_id' => $pull_request_id, diff --git a/app/Jobs/ProcessGithubPullRequestWebhook.php b/app/Jobs/ProcessGithubPullRequestWebhook.php index 01a512439..041cd812c 100644 --- a/app/Jobs/ProcessGithubPullRequestWebhook.php +++ b/app/Jobs/ProcessGithubPullRequestWebhook.php @@ -118,7 +118,7 @@ private function handleOpenAction(Application $application, ?GithubApp $githubAp if (! $found) { if ($application->build_pack === 'dockercompose') { - $preview = ApplicationPreview::forceCreate([ + $preview = ApplicationPreview::create([ 'git_type' => 'github', 'application_id' => $application->id, 'pull_request_id' => $this->pullRequestId, @@ -127,7 +127,7 @@ private function handleOpenAction(Application $application, ?GithubApp $githubAp ]); $preview->generate_preview_fqdn_compose(); } else { - $preview = ApplicationPreview::forceCreate([ + $preview = ApplicationPreview::create([ 'git_type' => 'github', 'application_id' => $application->id, 'pull_request_id' => $this->pullRequestId, diff --git a/app/Livewire/Boarding/Index.php b/app/Livewire/Boarding/Index.php index 170f0cdea..33c75bf70 100644 --- a/app/Livewire/Boarding/Index.php +++ b/app/Livewire/Boarding/Index.php @@ -441,7 +441,7 @@ public function selectExistingProject() public function createNewProject() { - $this->createdProject = Project::forceCreate([ + $this->createdProject = Project::create([ 'name' => 'My first project', 'team_id' => currentTeam()->id, 'uuid' => (string) new Cuid2, diff --git a/app/Livewire/Destination/New/Docker.php b/app/Livewire/Destination/New/Docker.php index 141235590..6f9b6f995 100644 --- a/app/Livewire/Destination/New/Docker.php +++ b/app/Livewire/Destination/New/Docker.php @@ -77,7 +77,7 @@ public function submit() if ($found) { throw new \Exception('Network already added to this server.'); } else { - $docker = SwarmDocker::forceCreate([ + $docker = SwarmDocker::create([ 'name' => $this->name, 'network' => $this->network, 'server_id' => $this->selectedServer->id, @@ -88,7 +88,7 @@ public function submit() if ($found) { throw new \Exception('Network already added to this server.'); } else { - $docker = StandaloneDocker::forceCreate([ + $docker = StandaloneDocker::create([ 'name' => $this->name, 'network' => $this->network, 'server_id' => $this->selectedServer->id, diff --git a/app/Livewire/ForcePasswordReset.php b/app/Livewire/ForcePasswordReset.php index 61a2a20e9..e6392497f 100644 --- a/app/Livewire/ForcePasswordReset.php +++ b/app/Livewire/ForcePasswordReset.php @@ -48,7 +48,7 @@ public function submit() $this->rateLimit(10); $this->validate(); $firstLogin = auth()->user()->created_at == auth()->user()->updated_at; - auth()->user()->forceFill([ + auth()->user()->fill([ 'password' => Hash::make($this->password), 'force_password_reset' => false, ])->save(); diff --git a/app/Livewire/Project/AddEmpty.php b/app/Livewire/Project/AddEmpty.php index a2581a5c9..974f0608a 100644 --- a/app/Livewire/Project/AddEmpty.php +++ b/app/Livewire/Project/AddEmpty.php @@ -30,7 +30,7 @@ public function submit() { try { $this->validate(); - $project = Project::forceCreate([ + $project = Project::create([ 'name' => $this->name, 'description' => $this->description, 'team_id' => currentTeam()->id, diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index c61a4e4a7..c887e9b83 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -196,7 +196,7 @@ public function add(int $pull_request_id, ?string $pull_request_html_url = null, $this->setDeploymentUuid(); $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); if (! $found && ! is_null($pull_request_html_url)) { - $found = ApplicationPreview::forceCreate([ + $found = ApplicationPreview::create([ 'application_id' => $this->application->id, 'pull_request_id' => $pull_request_id, 'pull_request_html_url' => $pull_request_html_url, @@ -210,7 +210,7 @@ public function add(int $pull_request_id, ?string $pull_request_html_url = null, $this->setDeploymentUuid(); $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); if (! $found && (! is_null($pull_request_html_url) || ($this->application->build_pack === 'dockerimage' && str($docker_registry_image_tag)->isNotEmpty()))) { - $found = ApplicationPreview::forceCreate([ + $found = ApplicationPreview::create([ 'application_id' => $this->application->id, 'pull_request_id' => $pull_request_id, 'pull_request_html_url' => $pull_request_html_url ?? '', @@ -262,7 +262,7 @@ public function deploy(int $pull_request_id, ?string $pull_request_html_url = nu $this->setDeploymentUuid(); $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); if (! $found && (! is_null($pull_request_html_url) || ($this->application->build_pack === 'dockerimage' && str($docker_registry_image_tag)->isNotEmpty()))) { - $found = ApplicationPreview::forceCreate([ + $found = ApplicationPreview::create([ 'application_id' => $this->application->id, 'pull_request_id' => $pull_request_id, 'pull_request_html_url' => $pull_request_html_url ?? '', diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index 93eb2a78c..644753c83 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -100,7 +100,7 @@ public function clone(string $type) if ($foundProject) { throw new \Exception('Project with the same name already exists.'); } - $project = Project::forceCreate([ + $project = Project::create([ 'name' => $this->newName, 'team_id' => currentTeam()->id, 'description' => $this->project->description.' (clone)', @@ -139,7 +139,7 @@ public function clone(string $type) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'uuid' => $uuid, 'status' => 'exited', 'started_at' => null, @@ -188,7 +188,7 @@ public function clone(string $type) 'created_at', 'updated_at', 'uuid', - ])->forceFill([ + ])->fill([ 'name' => $newName, 'resource_id' => $newDatabase->id, ]); @@ -217,7 +217,7 @@ public function clone(string $type) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'resource_id' => $newDatabase->id, ]); $newStorage->save(); @@ -230,7 +230,7 @@ public function clone(string $type) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'uuid' => $uuid, 'database_id' => $newDatabase->id, 'database_type' => $newDatabase->getMorphClass(), @@ -248,7 +248,7 @@ public function clone(string $type) 'id', 'created_at', 'updated_at', - ])->forceFill($payload); + ])->fill($payload); $newEnvironmentVariable->save(); } } @@ -259,7 +259,7 @@ public function clone(string $type) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'uuid' => $uuid, 'environment_id' => $environment->id, 'destination_id' => $this->selectedDestination, @@ -277,7 +277,7 @@ public function clone(string $type) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'uuid' => (string) new Cuid2, 'service_id' => $newService->id, 'team_id' => currentTeam()->id, @@ -291,7 +291,7 @@ public function clone(string $type) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'resourceable_id' => $newService->id, 'resourceable_type' => $newService->getMorphClass(), ]); @@ -299,7 +299,7 @@ public function clone(string $type) } foreach ($newService->applications() as $application) { - $application->forceFill([ + $application->fill([ 'status' => 'exited', ])->save(); @@ -317,7 +317,7 @@ public function clone(string $type) 'created_at', 'updated_at', 'uuid', - ])->forceFill([ + ])->fill([ 'name' => $newName, 'resource_id' => $application->id, ]); @@ -346,7 +346,7 @@ public function clone(string $type) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'resource_id' => $application->id, ]); $newStorage->save(); @@ -354,7 +354,7 @@ public function clone(string $type) } foreach ($newService->databases() as $database) { - $database->forceFill([ + $database->fill([ 'status' => 'exited', ])->save(); @@ -372,7 +372,7 @@ public function clone(string $type) 'created_at', 'updated_at', 'uuid', - ])->forceFill([ + ])->fill([ 'name' => $newName, 'resource_id' => $database->id, ]); @@ -401,7 +401,7 @@ public function clone(string $type) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'resource_id' => $database->id, ]); $newStorage->save(); @@ -414,7 +414,7 @@ public function clone(string $type) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'uuid' => $uuid, 'database_id' => $database->id, 'database_type' => $database->getMorphClass(), diff --git a/app/Livewire/Project/New/DockerCompose.php b/app/Livewire/Project/New/DockerCompose.php index 99fb2efc4..2b92902c6 100644 --- a/app/Livewire/Project/New/DockerCompose.php +++ b/app/Livewire/Project/New/DockerCompose.php @@ -54,7 +54,7 @@ public function submit() } $destination_class = $destination->getMorphClass(); - $service = Service::forceCreate([ + $service = Service::create([ 'docker_compose_raw' => $this->dockerComposeRaw, 'environment_id' => $environment->id, 'server_id' => (int) $server_id, diff --git a/app/Livewire/Project/New/DockerImage.php b/app/Livewire/Project/New/DockerImage.php index 8becdf585..268333d07 100644 --- a/app/Livewire/Project/New/DockerImage.php +++ b/app/Livewire/Project/New/DockerImage.php @@ -133,7 +133,7 @@ public function submit() // Determine the image tag based on whether it's a hash or regular tag $imageTag = $parser->isImageHash() ? 'sha256-'.$parser->getTag() : $parser->getTag(); - $application = Application::forceCreate([ + $application = Application::create([ 'name' => 'docker-image-'.new Cuid2, 'repository_project_id' => 0, 'git_repository' => 'coollabsio/coolify', diff --git a/app/Livewire/Project/New/EmptyProject.php b/app/Livewire/Project/New/EmptyProject.php index 1cdc7e098..0360365a9 100644 --- a/app/Livewire/Project/New/EmptyProject.php +++ b/app/Livewire/Project/New/EmptyProject.php @@ -10,7 +10,7 @@ class EmptyProject extends Component { public function createEmptyProject() { - $project = Project::forceCreate([ + $project = Project::create([ 'name' => generate_random_name(), 'team_id' => currentTeam()->id, 'uuid' => (string) new Cuid2, diff --git a/app/Livewire/Project/New/GithubPrivateRepository.php b/app/Livewire/Project/New/GithubPrivateRepository.php index 9d4acb9bb..0222008b0 100644 --- a/app/Livewire/Project/New/GithubPrivateRepository.php +++ b/app/Livewire/Project/New/GithubPrivateRepository.php @@ -191,7 +191,7 @@ public function submit() $project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->firstOrFail(); $environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail(); - $application = Application::forceCreate([ + $application = Application::create([ 'name' => generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name), 'repository_project_id' => $this->selected_repository_id, 'git_repository' => str($this->selected_repository_owner)->trim()->toString().'/'.str($this->selected_repository_repo)->trim()->toString(), diff --git a/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php b/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php index ba058c6ff..f8642d6fc 100644 --- a/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php +++ b/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php @@ -183,7 +183,7 @@ public function submit() $application_init['docker_compose_location'] = $this->docker_compose_location; $application_init['base_directory'] = $this->base_directory; } - $application = Application::forceCreate($application_init); + $application = Application::create($application_init); $application->settings->is_static = $this->is_static; $application->settings->save(); diff --git a/app/Livewire/Project/New/PublicGitRepository.php b/app/Livewire/Project/New/PublicGitRepository.php index 6bd71d246..62ac7ec0d 100644 --- a/app/Livewire/Project/New/PublicGitRepository.php +++ b/app/Livewire/Project/New/PublicGitRepository.php @@ -299,7 +299,7 @@ public function submit() $new_service['source_id'] = $this->git_source->id; $new_service['source_type'] = $this->git_source->getMorphClass(); } - $service = Service::forceCreate($new_service); + $service = Service::create($new_service); return redirect()->route('project.service.configuration', [ 'service_uuid' => $service->uuid, @@ -346,7 +346,7 @@ public function submit() $application_init['docker_compose_location'] = $this->docker_compose_location; $application_init['base_directory'] = $this->base_directory; } - $application = Application::forceCreate($application_init); + $application = Application::create($application_init); $application->settings->is_static = $this->isStatic; $application->settings->save(); diff --git a/app/Livewire/Project/New/SimpleDockerfile.php b/app/Livewire/Project/New/SimpleDockerfile.php index 400b58fea..1073157e6 100644 --- a/app/Livewire/Project/New/SimpleDockerfile.php +++ b/app/Livewire/Project/New/SimpleDockerfile.php @@ -52,7 +52,7 @@ public function submit() if (! $port) { $port = 80; } - $application = Application::forceCreate([ + $application = Application::create([ 'name' => 'dockerfile-'.new Cuid2, 'repository_project_id' => 0, 'git_repository' => 'coollabsio/coolify', diff --git a/app/Livewire/Project/Resource/Create.php b/app/Livewire/Project/Resource/Create.php index dbe56b079..966c66a14 100644 --- a/app/Livewire/Project/Resource/Create.php +++ b/app/Livewire/Project/Resource/Create.php @@ -91,7 +91,7 @@ public function mount() if (in_array($oneClickServiceName, NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK)) { data_set($service_payload, 'connect_to_docker_network', true); } - $service = Service::forceCreate($service_payload); + $service = Service::create($service_payload); $service->name = "$oneClickServiceName-".$service->uuid; $service->save(); if ($oneClickDotEnvs?->count() > 0) { diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index 301c51be9..f4813dd4c 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -94,7 +94,7 @@ public function cloneTo($destination_id) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'uuid' => $uuid, 'name' => $this->resource->name.'-clone-'.$uuid, 'status' => 'exited', @@ -143,7 +143,7 @@ public function cloneTo($destination_id) 'created_at', 'updated_at', 'uuid', - ])->forceFill([ + ])->fill([ 'name' => $newName, 'resource_id' => $new_resource->id, ]); @@ -172,7 +172,7 @@ public function cloneTo($destination_id) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'resource_id' => $new_resource->id, ]); $newStorage->save(); @@ -185,7 +185,7 @@ public function cloneTo($destination_id) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'uuid' => $uuid, 'database_id' => $new_resource->id, 'database_type' => $new_resource->getMorphClass(), @@ -204,7 +204,7 @@ public function cloneTo($destination_id) 'id', 'created_at', 'updated_at', - ])->forceFill($payload); + ])->fill($payload); $newEnvironmentVariable->save(); } @@ -221,7 +221,7 @@ public function cloneTo($destination_id) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'uuid' => $uuid, 'name' => $this->resource->name.'-clone-'.$uuid, 'destination_id' => $new_destination->id, @@ -242,7 +242,7 @@ public function cloneTo($destination_id) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'uuid' => (string) new Cuid2, 'service_id' => $new_resource->id, 'team_id' => currentTeam()->id, @@ -256,7 +256,7 @@ public function cloneTo($destination_id) 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'resourceable_id' => $new_resource->id, 'resourceable_type' => $new_resource->getMorphClass(), ]); @@ -264,7 +264,7 @@ public function cloneTo($destination_id) } foreach ($new_resource->applications() as $application) { - $application->forceFill([ + $application->fill([ 'status' => 'exited', ])->save(); @@ -282,7 +282,7 @@ public function cloneTo($destination_id) 'created_at', 'updated_at', 'uuid', - ])->forceFill([ + ])->fill([ 'name' => $newName, 'resource_id' => $application->id, ]); @@ -307,7 +307,7 @@ public function cloneTo($destination_id) } foreach ($new_resource->databases() as $database) { - $database->forceFill([ + $database->fill([ 'status' => 'exited', ])->save(); @@ -325,7 +325,7 @@ public function cloneTo($destination_id) 'created_at', 'updated_at', 'uuid', - ])->forceFill([ + ])->fill([ 'name' => $newName, 'resource_id' => $database->id, ]); @@ -366,7 +366,7 @@ public function moveTo($environment_id) try { $this->authorize('update', $this->resource); $new_environment = Environment::ownedByCurrentTeam()->findOrFail($environment_id); - $this->resource->forceFill([ + $this->resource->fill([ 'environment_id' => $environment_id, ])->save(); if ($this->resource->type() === 'application') { diff --git a/app/Livewire/Project/Show.php b/app/Livewire/Project/Show.php index b9628dd0d..e884abb4e 100644 --- a/app/Livewire/Project/Show.php +++ b/app/Livewire/Project/Show.php @@ -42,7 +42,7 @@ public function submit() { try { $this->validate(); - $environment = Environment::forceCreate([ + $environment = Environment::create([ 'name' => $this->name, 'project_id' => $this->project->id, 'uuid' => (string) new Cuid2, diff --git a/app/Livewire/Server/Destinations.php b/app/Livewire/Server/Destinations.php index f41ca00f3..117b43ad6 100644 --- a/app/Livewire/Server/Destinations.php +++ b/app/Livewire/Server/Destinations.php @@ -43,7 +43,7 @@ public function add($name) return; } else { - SwarmDocker::forceCreate([ + SwarmDocker::create([ 'name' => $this->server->name.'-'.$name, 'network' => $this->name, 'server_id' => $this->server->id, @@ -57,7 +57,7 @@ public function add($name) return; } else { - StandaloneDocker::forceCreate([ + StandaloneDocker::create([ 'name' => $this->server->name.'-'.$name, 'network' => $name, 'server_id' => $this->server->id, diff --git a/app/Models/Application.php b/app/Models/Application.php index bdc76eb33..fef6f6e4c 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -203,6 +203,14 @@ class Application extends BaseModel 'restart_count', 'last_restart_at', 'last_restart_type', + 'uuid', + 'environment_id', + 'destination_id', + 'destination_type', + 'source_id', + 'source_type', + 'repository_project_id', + 'private_key_id', ]; protected $appends = ['server_status']; @@ -262,7 +270,7 @@ protected static function booted() } } if (count($payload) > 0) { - $application->forceFill($payload); + $application->fill($payload); } // Buildpack switching cleanup logic @@ -299,7 +307,7 @@ protected static function booted() } }); static::created(function ($application) { - ApplicationSetting::forceCreate([ + ApplicationSetting::create([ 'application_id' => $application->id, ]); $application->compose_parsing_version = self::$parserVersion; diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php index 21cb58abe..67f28523c 100644 --- a/app/Models/ApplicationDeploymentQueue.php +++ b/app/Models/ApplicationDeploymentQueue.php @@ -44,6 +44,7 @@ class ApplicationDeploymentQueue extends Model 'application_id', 'deployment_uuid', 'pull_request_id', + 'docker_registry_image_tag', 'force_rebuild', 'commit', 'status', diff --git a/app/Models/ApplicationPreview.php b/app/Models/ApplicationPreview.php index 818f96d8e..f08a48cea 100644 --- a/app/Models/ApplicationPreview.php +++ b/app/Models/ApplicationPreview.php @@ -11,6 +11,7 @@ class ApplicationPreview extends BaseModel use SoftDeletes; protected $fillable = [ + 'uuid', 'application_id', 'pull_request_id', 'pull_request_html_url', @@ -62,7 +63,7 @@ protected static function booted() }); static::saving(function ($preview) { if ($preview->isDirty('status')) { - $preview->forceFill(['last_online_at' => now()]); + $preview->last_online_at = now(); } }); } diff --git a/app/Models/ApplicationSetting.php b/app/Models/ApplicationSetting.php index 24b35df7f..731a9b5da 100644 --- a/app/Models/ApplicationSetting.php +++ b/app/Models/ApplicationSetting.php @@ -29,6 +29,7 @@ class ApplicationSetting extends Model ]; protected $fillable = [ + 'application_id', 'is_static', 'is_git_submodules_enabled', 'is_git_lfs_enabled', diff --git a/app/Models/CloudProviderToken.php b/app/Models/CloudProviderToken.php index 123376c9b..026d11fba 100644 --- a/app/Models/CloudProviderToken.php +++ b/app/Models/CloudProviderToken.php @@ -5,6 +5,7 @@ class CloudProviderToken extends BaseModel { protected $fillable = [ + 'team_id', 'provider', 'token', 'name', diff --git a/app/Models/Environment.php b/app/Models/Environment.php index 55ce93265..65ffaf579 100644 --- a/app/Models/Environment.php +++ b/app/Models/Environment.php @@ -28,6 +28,8 @@ class Environment extends BaseModel protected $fillable = [ 'name', 'description', + 'project_id', + 'uuid', ]; protected static function booted() diff --git a/app/Models/GithubApp.php b/app/Models/GithubApp.php index 3cffeb8f8..54bbb3f7d 100644 --- a/app/Models/GithubApp.php +++ b/app/Models/GithubApp.php @@ -7,6 +7,8 @@ class GithubApp extends BaseModel { protected $fillable = [ + 'team_id', + 'private_key_id', 'name', 'organization', 'api_url', diff --git a/app/Models/Project.php b/app/Models/Project.php index ff2cae041..15628892e 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -27,6 +27,8 @@ class Project extends BaseModel protected $fillable = [ 'name', 'description', + 'team_id', + 'uuid', ]; /** @@ -51,10 +53,10 @@ public static function ownedByCurrentTeamCached() protected static function booted() { static::created(function ($project) { - ProjectSetting::forceCreate([ + ProjectSetting::create([ 'project_id' => $project->id, ]); - Environment::forceCreate([ + Environment::create([ 'name' => 'production', 'project_id' => $project->id, 'uuid' => (string) new Cuid2, diff --git a/app/Models/ProjectSetting.php b/app/Models/ProjectSetting.php index 7ea17ba7a..8b59ffac6 100644 --- a/app/Models/ProjectSetting.php +++ b/app/Models/ProjectSetting.php @@ -6,7 +6,9 @@ class ProjectSetting extends Model { - protected $fillable = []; + protected $fillable = [ + 'project_id', + ]; public function project() { diff --git a/app/Models/ScheduledDatabaseBackup.php b/app/Models/ScheduledDatabaseBackup.php index c6aed863d..6308bae8b 100644 --- a/app/Models/ScheduledDatabaseBackup.php +++ b/app/Models/ScheduledDatabaseBackup.php @@ -9,6 +9,8 @@ class ScheduledDatabaseBackup extends BaseModel { protected $fillable = [ + 'uuid', + 'team_id', 'description', 'enabled', 'save_s3', diff --git a/app/Models/ScheduledDatabaseBackupExecution.php b/app/Models/ScheduledDatabaseBackupExecution.php index f1f6e88b5..51ad46de9 100644 --- a/app/Models/ScheduledDatabaseBackupExecution.php +++ b/app/Models/ScheduledDatabaseBackupExecution.php @@ -7,6 +7,8 @@ class ScheduledDatabaseBackupExecution extends BaseModel { protected $fillable = [ + 'uuid', + 'scheduled_database_backup_id', 'status', 'message', 'size', diff --git a/app/Models/ScheduledTask.php b/app/Models/ScheduledTask.php index e76f1b7b9..40f8e1860 100644 --- a/app/Models/ScheduledTask.php +++ b/app/Models/ScheduledTask.php @@ -30,12 +30,16 @@ class ScheduledTask extends BaseModel use HasSafeStringAttribute; protected $fillable = [ + 'uuid', 'enabled', 'name', 'command', 'frequency', 'container', 'timeout', + 'team_id', + 'application_id', + 'service_id', ]; public static function ownedByCurrentTeamAPI(int $teamId) diff --git a/app/Models/ScheduledTaskExecution.php b/app/Models/ScheduledTaskExecution.php index dd74ba2e0..1e26c7be3 100644 --- a/app/Models/ScheduledTaskExecution.php +++ b/app/Models/ScheduledTaskExecution.php @@ -23,6 +23,7 @@ class ScheduledTaskExecution extends BaseModel { protected $fillable = [ + 'scheduled_task_id', 'status', 'message', 'finished_at', diff --git a/app/Models/Server.php b/app/Models/Server.php index 427896a19..a18fe14ae 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -135,7 +135,7 @@ protected static function booted() $payload['ip_previous'] = $server->getOriginal('ip'); } } - $server->forceFill($payload); + $server->fill($payload); }); static::saved(function ($server) { if ($server->wasChanged('private_key_id') || $server->privateKey?->isDirty()) { @@ -265,6 +265,7 @@ public static function flushIdentityMap(): void 'detected_traefik_version', 'traefik_outdated_info', 'server_metadata', + 'ip_previous', ]; use HasSafeStringAttribute; diff --git a/app/Models/ServerSetting.php b/app/Models/ServerSetting.php index d34f2c86b..30fc1e165 100644 --- a/app/Models/ServerSetting.php +++ b/app/Models/ServerSetting.php @@ -54,6 +54,7 @@ class ServerSetting extends Model { protected $fillable = [ + 'server_id', 'is_swarm_manager', 'is_jump_server', 'is_build_server', diff --git a/app/Models/Service.php b/app/Models/Service.php index 491924c49..11189b4ac 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -49,6 +49,7 @@ class Service extends BaseModel private static $parserVersion = '5'; protected $fillable = [ + 'uuid', 'name', 'description', 'docker_compose_raw', @@ -58,6 +59,10 @@ class Service extends BaseModel 'config_hash', 'compose_parsing_version', 'is_container_label_escape_enabled', + 'environment_id', + 'server_id', + 'destination_id', + 'destination_type', ]; protected $appends = ['server_status', 'status']; diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php index e608c202d..6bf12f4e7 100644 --- a/app/Models/ServiceApplication.php +++ b/app/Models/ServiceApplication.php @@ -12,6 +12,7 @@ class ServiceApplication extends BaseModel use HasFactory, SoftDeletes; protected $fillable = [ + 'service_id', 'name', 'human_name', 'description', @@ -39,7 +40,7 @@ protected static function booted() }); static::saving(function ($service) { if ($service->isDirty('status')) { - $service->forceFill(['last_online_at' => now()]); + $service->last_online_at = now(); } }); } diff --git a/app/Models/ServiceDatabase.php b/app/Models/ServiceDatabase.php index e5b28d929..69801f985 100644 --- a/app/Models/ServiceDatabase.php +++ b/app/Models/ServiceDatabase.php @@ -10,6 +10,7 @@ class ServiceDatabase extends BaseModel use HasFactory, SoftDeletes; protected $fillable = [ + 'service_id', 'name', 'human_name', 'description', @@ -44,7 +45,7 @@ protected static function booted() }); static::saving(function ($service) { if ($service->isDirty('status')) { - $service->forceFill(['last_online_at' => now()]); + $service->last_online_at = now(); } }); } diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php index c6d91dd55..784e2c937 100644 --- a/app/Models/StandaloneClickhouse.php +++ b/app/Models/StandaloneClickhouse.php @@ -14,6 +14,7 @@ class StandaloneClickhouse extends BaseModel use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; protected $fillable = [ + 'uuid', 'name', 'description', 'clickhouse_admin_user', @@ -40,6 +41,9 @@ class StandaloneClickhouse extends BaseModel 'public_port_timeout', 'custom_docker_run_options', 'clickhouse_db', + 'destination_type', + 'destination_id', + 'environment_id', ]; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; @@ -71,7 +75,7 @@ protected static function booted() }); static::saving(function ($database) { if ($database->isDirty('status')) { - $database->forceFill(['last_online_at' => now()]); + $database->last_online_at = now(); } }); } diff --git a/app/Models/StandaloneDocker.php b/app/Models/StandaloneDocker.php index 09dae022b..dcb349405 100644 --- a/app/Models/StandaloneDocker.php +++ b/app/Models/StandaloneDocker.php @@ -13,6 +13,7 @@ class StandaloneDocker extends BaseModel use HasSafeStringAttribute; protected $fillable = [ + 'server_id', 'name', 'network', ]; diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php index af309f980..e07053c03 100644 --- a/app/Models/StandaloneDragonfly.php +++ b/app/Models/StandaloneDragonfly.php @@ -14,6 +14,7 @@ class StandaloneDragonfly extends BaseModel use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; protected $fillable = [ + 'uuid', 'name', 'description', 'dragonfly_password', @@ -39,6 +40,9 @@ class StandaloneDragonfly extends BaseModel 'public_port_timeout', 'enable_ssl', 'custom_docker_run_options', + 'destination_type', + 'destination_id', + 'environment_id', ]; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; @@ -70,7 +74,7 @@ protected static function booted() }); static::saving(function ($database) { if ($database->isDirty('status')) { - $database->forceFill(['last_online_at' => now()]); + $database->last_online_at = now(); } }); } diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php index ee07b4783..979f45a3d 100644 --- a/app/Models/StandaloneKeydb.php +++ b/app/Models/StandaloneKeydb.php @@ -14,6 +14,7 @@ class StandaloneKeydb extends BaseModel use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; protected $fillable = [ + 'uuid', 'name', 'description', 'keydb_password', @@ -40,6 +41,9 @@ class StandaloneKeydb extends BaseModel 'public_port_timeout', 'enable_ssl', 'custom_docker_run_options', + 'destination_type', + 'destination_id', + 'environment_id', ]; protected $appends = ['internal_db_url', 'external_db_url', 'server_status']; @@ -71,7 +75,7 @@ protected static function booted() }); static::saving(function ($database) { if ($database->isDirty('status')) { - $database->forceFill(['last_online_at' => now()]); + $database->last_online_at = now(); } }); } diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php index ad5220496..dba8a52f5 100644 --- a/app/Models/StandaloneMariadb.php +++ b/app/Models/StandaloneMariadb.php @@ -15,6 +15,7 @@ class StandaloneMariadb extends BaseModel use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; protected $fillable = [ + 'uuid', 'name', 'description', 'mariadb_root_password', @@ -43,6 +44,9 @@ class StandaloneMariadb extends BaseModel 'enable_ssl', 'is_log_drain_enabled', 'custom_docker_run_options', + 'destination_type', + 'destination_id', + 'environment_id', ]; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; @@ -74,7 +78,7 @@ protected static function booted() }); static::saving(function ($database) { if ($database->isDirty('status')) { - $database->forceFill(['last_online_at' => now()]); + $database->last_online_at = now(); } }); } diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php index 590c173e1..e72f4f1c6 100644 --- a/app/Models/StandaloneMongodb.php +++ b/app/Models/StandaloneMongodb.php @@ -14,6 +14,7 @@ class StandaloneMongodb extends BaseModel use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; protected $fillable = [ + 'uuid', 'name', 'description', 'mongo_conf', @@ -43,6 +44,9 @@ class StandaloneMongodb extends BaseModel 'is_log_drain_enabled', 'is_include_timestamps', 'custom_docker_run_options', + 'destination_type', + 'destination_id', + 'environment_id', ]; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; @@ -80,7 +84,7 @@ protected static function booted() }); static::saving(function ($database) { if ($database->isDirty('status')) { - $database->forceFill(['last_online_at' => now()]); + $database->last_online_at = now(); } }); } diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php index d991617b7..1c522d200 100644 --- a/app/Models/StandaloneMysql.php +++ b/app/Models/StandaloneMysql.php @@ -14,6 +14,7 @@ class StandaloneMysql extends BaseModel use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; protected $fillable = [ + 'uuid', 'name', 'description', 'mysql_root_password', @@ -44,6 +45,9 @@ class StandaloneMysql extends BaseModel 'is_log_drain_enabled', 'is_include_timestamps', 'custom_docker_run_options', + 'destination_type', + 'destination_id', + 'environment_id', ]; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; @@ -76,7 +80,7 @@ protected static function booted() }); static::saving(function ($database) { if ($database->isDirty('status')) { - $database->forceFill(['last_online_at' => now()]); + $database->last_online_at = now(); } }); } diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index 71034427f..57dfe5988 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -14,6 +14,7 @@ class StandalonePostgresql extends BaseModel use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; protected $fillable = [ + 'uuid', 'name', 'description', 'postgres_user', @@ -46,6 +47,9 @@ class StandalonePostgresql extends BaseModel 'is_log_drain_enabled', 'is_include_timestamps', 'custom_docker_run_options', + 'destination_type', + 'destination_id', + 'environment_id', ]; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; @@ -92,7 +96,7 @@ protected static function booted() }); static::saving(function ($database) { if ($database->isDirty('status')) { - $database->forceFill(['last_online_at' => now()]); + $database->last_online_at = now(); } }); } diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index 4eb28e038..ef42d7f18 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -14,6 +14,7 @@ class StandaloneRedis extends BaseModel use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; protected $fillable = [ + 'uuid', 'name', 'description', 'redis_conf', @@ -39,6 +40,9 @@ class StandaloneRedis extends BaseModel 'is_log_drain_enabled', 'is_include_timestamps', 'custom_docker_run_options', + 'destination_type', + 'destination_id', + 'environment_id', ]; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; @@ -69,7 +73,7 @@ protected static function booted() }); static::saving(function ($database) { if ($database->isDirty('status')) { - $database->forceFill(['last_online_at' => now()]); + $database->last_online_at = now(); } }); diff --git a/app/Models/Subscription.php b/app/Models/Subscription.php index fa135b29f..b0fec64f9 100644 --- a/app/Models/Subscription.php +++ b/app/Models/Subscription.php @@ -7,6 +7,7 @@ class Subscription extends Model { protected $fillable = [ + 'team_id', 'stripe_invoice_paid', 'stripe_subscription_id', 'stripe_customer_id', diff --git a/app/Models/SwarmDocker.php b/app/Models/SwarmDocker.php index 656749119..134e36189 100644 --- a/app/Models/SwarmDocker.php +++ b/app/Models/SwarmDocker.php @@ -7,6 +7,7 @@ class SwarmDocker extends BaseModel { protected $fillable = [ + 'server_id', 'name', 'network', ]; diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 9ee58cf7d..e6fbd3a06 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -10,6 +10,7 @@ class Tag extends BaseModel protected $fillable = [ 'name', + 'team_id', ]; protected function customizeName($value) diff --git a/app/Models/User.php b/app/Models/User.php index ad9a7af31..aa33a49fb 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -49,6 +49,9 @@ class User extends Authenticatable implements SendsEmail 'password', 'force_password_reset', 'marketing_emails', + 'pending_email', + 'email_change_code', + 'email_change_code_expires_at', ]; protected $hidden = [ @@ -409,7 +412,7 @@ public function requestEmailChange(string $newEmail): void $expiryMinutes = config('constants.email_change.verification_code_expiry_minutes', 10); $expiresAt = Carbon::now()->addMinutes($expiryMinutes); - $this->forceFill([ + $this->fill([ 'pending_email' => $newEmail, 'email_change_code' => $code, 'email_change_code_expires_at' => $expiresAt, diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index e4feec692..48e0a8c78 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -214,7 +214,7 @@ function clone_application(Application $source, $destination, array $overrides = 'updated_at', 'additional_servers_count', 'additional_networks_count', - ])->forceFill(array_merge([ + ])->fill(array_merge([ 'uuid' => $uuid, 'name' => $name, 'fqdn' => $url, @@ -237,7 +237,7 @@ function clone_application(Application $source, $destination, array $overrides = 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'application_id' => $newApplication->id, ]); $newApplicationSettings->save(); @@ -257,7 +257,7 @@ function clone_application(Application $source, $destination, array $overrides = 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'uuid' => (string) new Cuid2, 'application_id' => $newApplication->id, 'team_id' => currentTeam()->id, @@ -272,7 +272,7 @@ function clone_application(Application $source, $destination, array $overrides = 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'uuid' => (string) new Cuid2, 'application_id' => $newApplication->id, 'status' => 'exited', @@ -304,7 +304,7 @@ function clone_application(Application $source, $destination, array $overrides = 'created_at', 'updated_at', 'uuid', - ])->forceFill([ + ])->fill([ 'name' => $newName, 'resource_id' => $newApplication->id, ]); @@ -340,7 +340,7 @@ function clone_application(Application $source, $destination, array $overrides = 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'resource_id' => $newApplication->id, ]); $newStorage->save(); @@ -354,7 +354,7 @@ function clone_application(Application $source, $destination, array $overrides = 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'resourceable_id' => $newApplication->id, 'resourceable_type' => $newApplication->getMorphClass(), 'is_preview' => false, @@ -371,7 +371,7 @@ function clone_application(Application $source, $destination, array $overrides = 'id', 'created_at', 'updated_at', - ])->forceFill([ + ])->fill([ 'resourceable_id' => $newApplication->id, 'resourceable_type' => $newApplication->getMorphClass(), 'is_preview' => true, diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php index 751851283..123cf906a 100644 --- a/bootstrap/helpers/parsers.php +++ b/bootstrap/helpers/parsers.php @@ -1597,7 +1597,7 @@ function serviceParser(Service $resource): Collection if ($databaseFound) { $savedService = $databaseFound; } else { - $savedService = ServiceDatabase::forceCreate([ + $savedService = ServiceDatabase::create([ 'name' => $serviceName, 'service_id' => $resource->id, ]); @@ -1607,7 +1607,7 @@ function serviceParser(Service $resource): Collection if ($applicationFound) { $savedService = $applicationFound; } else { - $savedService = ServiceApplication::forceCreate([ + $savedService = ServiceApplication::create([ 'name' => $serviceName, 'service_id' => $resource->id, ]); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index a43f2e340..cd773f6a9 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1919,7 +1919,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal // Create new serviceApplication or serviceDatabase if ($isDatabase) { if ($isNew) { - $savedService = ServiceDatabase::forceCreate([ + $savedService = ServiceDatabase::create([ 'name' => $serviceName, 'image' => $image, 'service_id' => $resource->id, @@ -1930,7 +1930,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'service_id' => $resource->id, ])->first(); if (is_null($savedService)) { - $savedService = ServiceDatabase::forceCreate([ + $savedService = ServiceDatabase::create([ 'name' => $serviceName, 'image' => $image, 'service_id' => $resource->id, @@ -1939,7 +1939,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } else { if ($isNew) { - $savedService = ServiceApplication::forceCreate([ + $savedService = ServiceApplication::create([ 'name' => $serviceName, 'image' => $image, 'service_id' => $resource->id, @@ -1950,7 +1950,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'service_id' => $resource->id, ])->first(); if (is_null($savedService)) { - $savedService = ServiceApplication::forceCreate([ + $savedService = ServiceApplication::create([ 'name' => $serviceName, 'image' => $image, 'service_id' => $resource->id, diff --git a/tests/Feature/ApplicationHealthCheckApiTest.php b/tests/Feature/ApplicationHealthCheckApiTest.php index 7f1b985ad..3e4078051 100644 --- a/tests/Feature/ApplicationHealthCheckApiTest.php +++ b/tests/Feature/ApplicationHealthCheckApiTest.php @@ -31,7 +31,7 @@ ); }); - $this->project = Project::forceCreate([ + $this->project = Project::create([ 'uuid' => (string) new Cuid2, 'name' => 'test-project', 'team_id' => $this->team->id, diff --git a/tests/Feature/ApplicationRollbackTest.php b/tests/Feature/ApplicationRollbackTest.php index 61b3505ae..432bdde1b 100644 --- a/tests/Feature/ApplicationRollbackTest.php +++ b/tests/Feature/ApplicationRollbackTest.php @@ -6,7 +6,7 @@ describe('Application Rollback', function () { beforeEach(function () { $this->application = new Application; - $this->application->forceFill([ + $this->application->fill([ 'uuid' => 'test-app-uuid', 'git_commit_sha' => 'HEAD', ]); diff --git a/tests/Feature/ClonePersistentVolumeUuidTest.php b/tests/Feature/ClonePersistentVolumeUuidTest.php index 3f99c5585..13f7a1396 100644 --- a/tests/Feature/ClonePersistentVolumeUuidTest.php +++ b/tests/Feature/ClonePersistentVolumeUuidTest.php @@ -31,7 +31,7 @@ 'redirect' => 'both', ]); - $this->application->settings->forceFill([ + $this->application->settings->fill([ 'is_container_label_readonly_enabled' => false, ])->save(); @@ -92,7 +92,7 @@ }); test('cloning application reassigns settings to the cloned application', function () { - $this->application->settings->forceFill([ + $this->application->settings->fill([ 'is_static' => true, 'is_spa' => true, 'is_build_server_enabled' => true, @@ -118,7 +118,7 @@ }); test('cloning application reassigns scheduled tasks and previews to the cloned application', function () { - $scheduledTask = ScheduledTask::forceCreate([ + $scheduledTask = ScheduledTask::create([ 'uuid' => 'scheduled-task-original', 'application_id' => $this->application->id, 'team_id' => $this->team->id, @@ -129,7 +129,7 @@ 'timeout' => 120, ]); - $preview = ApplicationPreview::forceCreate([ + $preview = ApplicationPreview::create([ 'uuid' => 'preview-original', 'application_id' => $this->application->id, 'pull_request_id' => 123, diff --git a/tests/Feature/ComposePreviewFqdnTest.php b/tests/Feature/ComposePreviewFqdnTest.php index 62fc0f2d8..a5b8b2c9f 100644 --- a/tests/Feature/ComposePreviewFqdnTest.php +++ b/tests/Feature/ComposePreviewFqdnTest.php @@ -14,7 +14,7 @@ ]), ]); - $preview = ApplicationPreview::forceCreate([ + $preview = ApplicationPreview::create([ 'application_id' => $application->id, 'pull_request_id' => 42, 'pull_request_html_url' => 'https://github.com/example/repo/pull/42', @@ -39,7 +39,7 @@ ]), ]); - $preview = ApplicationPreview::forceCreate([ + $preview = ApplicationPreview::create([ 'application_id' => $application->id, 'pull_request_id' => 7, 'pull_request_html_url' => 'https://github.com/example/repo/pull/7', @@ -65,7 +65,7 @@ ]), ]); - $preview = ApplicationPreview::forceCreate([ + $preview = ApplicationPreview::create([ 'application_id' => $application->id, 'pull_request_id' => 99, 'pull_request_html_url' => 'https://github.com/example/repo/pull/99', diff --git a/tests/Feature/DatabaseEnvironmentVariableApiTest.php b/tests/Feature/DatabaseEnvironmentVariableApiTest.php index 78e80483b..f3297cf17 100644 --- a/tests/Feature/DatabaseEnvironmentVariableApiTest.php +++ b/tests/Feature/DatabaseEnvironmentVariableApiTest.php @@ -33,7 +33,7 @@ function createDatabase($context): StandalonePostgresql { - return StandalonePostgresql::forceCreate([ + return StandalonePostgresql::create([ 'name' => 'test-postgres', 'image' => 'postgres:15-alpine', 'postgres_user' => 'postgres', diff --git a/tests/Feature/DatabasePublicPortTimeoutApiTest.php b/tests/Feature/DatabasePublicPortTimeoutApiTest.php index 1ffc32a81..6bbc6279f 100644 --- a/tests/Feature/DatabasePublicPortTimeoutApiTest.php +++ b/tests/Feature/DatabasePublicPortTimeoutApiTest.php @@ -33,7 +33,7 @@ describe('PATCH /api/v1/databases', function () { test('updates public_port_timeout on a postgresql database', function () { - $database = StandalonePostgresql::forceCreate([ + $database = StandalonePostgresql::create([ 'name' => 'test-postgres', 'image' => 'postgres:15-alpine', 'postgres_user' => 'postgres', @@ -57,7 +57,7 @@ }); test('updates public_port_timeout on a redis database', function () { - $database = StandaloneRedis::forceCreate([ + $database = StandaloneRedis::create([ 'name' => 'test-redis', 'image' => 'redis:7', 'redis_password' => 'password', @@ -79,7 +79,7 @@ }); test('rejects invalid public_port_timeout value', function () { - $database = StandalonePostgresql::forceCreate([ + $database = StandalonePostgresql::create([ 'name' => 'test-postgres', 'image' => 'postgres:15-alpine', 'postgres_user' => 'postgres', @@ -101,7 +101,7 @@ }); test('accepts null public_port_timeout', function () { - $database = StandalonePostgresql::forceCreate([ + $database = StandalonePostgresql::create([ 'name' => 'test-postgres', 'image' => 'postgres:15-alpine', 'postgres_user' => 'postgres', diff --git a/tests/Feature/DatabaseSslStatusRefreshTest.php b/tests/Feature/DatabaseSslStatusRefreshTest.php index eab2b08db..e62ef48ad 100644 --- a/tests/Feature/DatabaseSslStatusRefreshTest.php +++ b/tests/Feature/DatabaseSslStatusRefreshTest.php @@ -52,7 +52,7 @@ $project = Project::factory()->create(['team_id' => $this->team->id]); $environment = Environment::factory()->create(['project_id' => $project->id]); - $database = StandaloneMysql::forceCreate([ + $database = StandaloneMysql::create([ 'name' => 'test-mysql', 'image' => 'mysql:8', 'mysql_root_password' => 'password', @@ -70,7 +70,7 @@ $component = Livewire::test(MysqlGeneral::class, ['database' => $database]) ->assertDontSee('Database should be stopped to change this settings.'); - $database->forceFill(['status' => 'running:healthy'])->save(); + $database->fill(['status' => 'running:healthy'])->save(); $component->call('refresh') ->assertSee('Database should be stopped to change this settings.'); diff --git a/tests/Feature/GetLogsCommandInjectionTest.php b/tests/Feature/GetLogsCommandInjectionTest.php index 3e5a33b66..c0b17c3bd 100644 --- a/tests/Feature/GetLogsCommandInjectionTest.php +++ b/tests/Feature/GetLogsCommandInjectionTest.php @@ -69,7 +69,7 @@ describe('GetLogs Livewire action validation', function () { test('getLogs rejects invalid container name', function () { // Make server functional by setting settings directly - $this->server->settings->forceFill([ + $this->server->settings->fill([ 'is_reachable' => true, 'is_usable' => true, 'force_disabled' => false, @@ -100,7 +100,7 @@ }); test('downloadAllLogs returns empty for invalid container name', function () { - $this->server->settings->forceFill([ + $this->server->settings->fill([ 'is_reachable' => true, 'is_usable' => true, 'force_disabled' => false, diff --git a/tests/Feature/GithubPrivateRepositoryTest.php b/tests/Feature/GithubPrivateRepositoryTest.php index abc288519..ba66a10bb 100644 --- a/tests/Feature/GithubPrivateRepositoryTest.php +++ b/tests/Feature/GithubPrivateRepositoryTest.php @@ -31,7 +31,7 @@ 'team_id' => $this->team->id, ]); - $this->githubApp = GithubApp::forceCreate([ + $this->githubApp = GithubApp::create([ 'name' => 'Test GitHub App', 'api_url' => 'https://api.github.com', 'html_url' => 'https://github.com', diff --git a/tests/Feature/InternalModelCreationMassAssignmentTest.php b/tests/Feature/InternalModelCreationMassAssignmentTest.php index fc581bf5c..5aad7f3e0 100644 --- a/tests/Feature/InternalModelCreationMassAssignmentTest.php +++ b/tests/Feature/InternalModelCreationMassAssignmentTest.php @@ -24,7 +24,7 @@ ]); $destination = $server->standaloneDockers()->firstOrFail(); - $application = Application::forceCreate([ + $application = Application::create([ 'name' => 'internal-app', 'git_repository' => 'https://github.com/coollabsio/coolify', 'git_branch' => 'main', @@ -57,7 +57,7 @@ ]); $destination = $server->standaloneDockers()->firstOrFail(); - $service = Service::forceCreate([ + $service = Service::create([ 'docker_compose_raw' => 'services: {}', 'environment_id' => $environment->id, 'server_id' => $server->id, diff --git a/tests/Feature/ModelFillableCreationTest.php b/tests/Feature/ModelFillableCreationTest.php new file mode 100644 index 000000000..b72e7381e --- /dev/null +++ b/tests/Feature/ModelFillableCreationTest.php @@ -0,0 +1,1114 @@ +team = Team::factory()->create(); + $this->server = Server::factory()->create(['team_id' => $this->team->id]); + $this->destination = $this->server->standaloneDockers()->firstOrFail(); + $this->project = Project::factory()->create(['team_id' => $this->team->id]); + $this->environment = Environment::factory()->create(['project_id' => $this->project->id]); +}); + +it('creates User with all fillable attributes', function () { + $user = User::create([ + 'name' => 'Test User', + 'email' => 'fillable-test@example.com', + 'password' => bcrypt('password123'), + 'force_password_reset' => true, + 'marketing_emails' => false, + 'pending_email' => 'newemail@example.com', + 'email_change_code' => 'ABC123', + 'email_change_code_expires_at' => now()->addHour(), + ]); + + expect($user->exists)->toBeTrue(); + expect($user->name)->toBe('Test User'); + expect($user->email)->toBe('fillable-test@example.com'); + expect($user->force_password_reset)->toBeTrue(); + expect($user->marketing_emails)->toBeFalse(); + expect($user->pending_email)->toBe('newemail@example.com'); + expect($user->email_change_code)->toBe('ABC123'); + expect($user->email_change_code_expires_at)->not->toBeNull(); +}); + +it('creates Server with all fillable attributes', function () { + $cloudToken = CloudProviderToken::create([ + 'team_id' => $this->team->id, + 'provider' => 'hetzner', + 'token' => 'test-token', + 'name' => 'test-cloud', + ]); + + $server = Server::create([ + 'name' => 'fillable-test-server', + 'ip' => '10.0.0.99', + 'port' => 2222, + 'user' => 'deployer', + 'description' => 'A test server with all fillable attrs', + 'private_key_id' => $this->server->private_key_id, + 'cloud_provider_token_id' => $cloudToken->id, + 'team_id' => $this->team->id, + 'hetzner_server_id' => 'htz-12345', + 'hetzner_server_status' => 'running', + 'is_validating' => false, + 'detected_traefik_version' => 'v2.10.0', + 'traefik_outdated_info' => 'Up to date', + 'server_metadata' => '{"region":"eu-central"}', + 'ip_previous' => '10.0.0.1', + ]); + + expect($server->exists)->toBeTrue(); + expect((string) $server->name)->toBe('fillable-test-server'); + expect((string) $server->ip)->toBe('10.0.0.99'); + expect($server->port)->toBe(2222); + expect((string) $server->user)->toBe('deployer'); + expect((string) $server->description)->toBe('A test server with all fillable attrs'); + expect($server->private_key_id)->toBe($this->server->private_key_id); + expect($server->cloud_provider_token_id)->toBe($cloudToken->id); + expect($server->hetzner_server_id)->toBe('htz-12345'); + expect($server->hetzner_server_status)->toBe('running'); + expect($server->ip_previous)->toBe('10.0.0.1'); +}); + +it('creates Project with all fillable attributes', function () { + $project = Project::create([ + 'name' => 'Fillable Test Project', + 'description' => 'Testing all fillable attrs', + 'team_id' => $this->team->id, + 'uuid' => 'custom-project-uuid', + ]); + + expect($project->exists)->toBeTrue(); + expect($project->name)->toBe('Fillable Test Project'); + expect($project->description)->toBe('Testing all fillable attrs'); + expect($project->team_id)->toBe($this->team->id); + expect($project->uuid)->toBe('custom-project-uuid'); +}); + +it('creates Environment with all fillable attributes', function () { + $env = Environment::create([ + 'name' => 'staging', + 'description' => 'Staging environment', + 'project_id' => $this->project->id, + 'uuid' => 'custom-env-uuid', + ]); + + expect($env->exists)->toBeTrue(); + expect($env->name)->toBe('staging'); + expect($env->description)->toBe('Staging environment'); + expect($env->project_id)->toBe($this->project->id); + expect($env->uuid)->toBe('custom-env-uuid'); +}); + +it('creates ProjectSetting with all fillable attributes', function () { + $setting = ProjectSetting::create([ + 'project_id' => $this->project->id, + ]); + + expect($setting->exists)->toBeTrue(); + expect($setting->project_id)->toBe($this->project->id); +}); + +it('creates Application with all fillable attributes', function () { + $application = Application::create([ + 'uuid' => 'custom-app-uuid', + 'name' => 'Full Fillable App', + 'description' => 'App with every fillable attr set', + 'fqdn' => 'https://app.example.com', + 'git_repository' => 'https://github.com/coollabsio/coolify', + 'git_branch' => 'main', + 'git_commit_sha' => 'abc123def456', + 'git_full_url' => 'https://github.com/coollabsio/coolify.git', + 'docker_registry_image_name' => 'ghcr.io/coollabsio/coolify', + 'docker_registry_image_tag' => 'latest', + 'build_pack' => 'nixpacks', + 'static_image' => 'nginx:alpine', + 'install_command' => 'npm install', + 'build_command' => 'npm run build', + 'start_command' => 'npm start', + 'ports_exposes' => '3000', + 'ports_mappings' => '3000:3000', + 'base_directory' => '/', + 'publish_directory' => '/dist', + 'health_check_enabled' => true, + 'health_check_path' => '/health', + 'health_check_port' => '3000', + 'health_check_host' => 'localhost', + 'health_check_method' => 'GET', + 'health_check_return_code' => 200, + 'health_check_scheme' => 'http', + 'health_check_response_text' => 'ok', + 'health_check_interval' => 30, + 'health_check_timeout' => 5, + 'health_check_retries' => 3, + 'health_check_start_period' => 10, + 'health_check_type' => 'http', + 'health_check_command' => 'curl -f http://localhost:3000/health', + 'limits_memory' => '512m', + 'limits_memory_swap' => '1g', + 'limits_memory_swappiness' => 60, + 'limits_memory_reservation' => '256m', + 'limits_cpus' => '2', + 'limits_cpuset' => '0-1', + 'limits_cpu_shares' => 1024, + 'status' => 'running', + 'preview_url_template' => '{{pr_id}}.{{domain}}', + 'dockerfile' => 'FROM node:18\nRUN npm install', + 'dockerfile_location' => '/Dockerfile', + 'dockerfile_target_build' => 'production', + 'custom_labels' => 'traefik.enable=true', + 'custom_docker_run_options' => '--cap-add=NET_ADMIN', + 'post_deployment_command' => 'php artisan migrate', + 'post_deployment_command_container' => 'app', + 'pre_deployment_command' => 'php artisan down', + 'pre_deployment_command_container' => 'app', + 'manual_webhook_secret_github' => 'gh-secret-123', + 'manual_webhook_secret_gitlab' => 'gl-secret-456', + 'manual_webhook_secret_bitbucket' => 'bb-secret-789', + 'manual_webhook_secret_gitea' => 'gt-secret-012', + 'docker_compose_location' => '/docker-compose.yml', + 'docker_compose' => 'services: {}', + 'docker_compose_raw' => 'services:\n app:\n image: nginx', + 'docker_compose_domains' => '{"app":"https://app.example.com"}', + 'docker_compose_custom_start_command' => 'docker compose up -d', + 'docker_compose_custom_build_command' => 'docker compose build', + 'swarm_replicas' => 3, + 'swarm_placement_constraints' => 'node.role==worker', + 'watch_paths' => 'src/**,package.json', + 'redirect' => 'www', + 'compose_parsing_version' => '2', + 'custom_nginx_configuration' => 'location / { proxy_pass http://localhost:3000; }', + 'custom_network_aliases' => 'app-alias', + 'custom_healthcheck_found' => false, + // Note: nixpkgsarchive, connect_to_docker_network, force_domain_override, + // is_container_label_escape_enabled, use_build_server are in $fillable but + // their migration columns may not exist in the test SQLite schema yet. + 'is_http_basic_auth_enabled' => false, + 'http_basic_auth_username' => 'admin', + 'http_basic_auth_password' => 'secret', + 'config_hash' => 'sha256:abc123', + 'last_online_at' => now()->subMinutes(5)->toISOString(), + 'restart_count' => 2, + 'last_restart_at' => now()->subHour()->toISOString(), + 'last_restart_type' => 'manual', + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + 'source_id' => null, + 'source_type' => null, + 'repository_project_id' => null, + 'private_key_id' => null, + ]); + + expect($application->exists)->toBeTrue(); + expect($application->uuid)->toBe('custom-app-uuid'); + expect($application->name)->toBe('Full Fillable App'); + expect((string) $application->git_repository)->toBe('https://github.com/coollabsio/coolify'); + expect($application->build_pack)->toBe('nixpacks'); + expect($application->ports_exposes)->toBe('3000'); + expect($application->environment_id)->toBe($this->environment->id); + expect($application->destination_id)->toBe($this->destination->id); + expect($application->health_check_enabled)->toBeTrue(); + expect($application->limits_memory)->toBe('512m'); + expect($application->swarm_replicas)->toBe(3); + expect($application->restart_count)->toBe(2); +}); + +it('creates ApplicationSetting with all fillable attributes', function () { + $app = Application::create([ + 'name' => 'settings-test-app', + 'git_repository' => 'https://github.com/test/repo', + 'git_branch' => 'main', + 'build_pack' => 'nixpacks', + 'ports_exposes' => '3000', + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + // Delete auto-created setting so we can create one with all attrs + ApplicationSetting::where('application_id', $app->id)->delete(); + + $setting = ApplicationSetting::create([ + 'application_id' => $app->id, + 'is_static' => true, + 'is_git_submodules_enabled' => true, + 'is_git_lfs_enabled' => true, + 'is_auto_deploy_enabled' => false, + 'is_force_https_enabled' => true, + 'is_debug_enabled' => true, + 'is_preview_deployments_enabled' => false, + 'is_log_drain_enabled' => true, + 'is_gpu_enabled' => true, + 'gpu_driver' => 'nvidia', + 'gpu_count' => '2', + 'gpu_device_ids' => 'GPU-abc,GPU-def', + 'gpu_options' => '--gpus all', + 'is_include_timestamps' => true, + 'is_swarm_only_worker_nodes' => false, + 'is_raw_compose_deployment_enabled' => false, + 'is_build_server_enabled' => false, + 'is_consistent_container_name_enabled' => true, + 'is_gzip_enabled' => true, + 'is_stripprefix_enabled' => true, + 'connect_to_docker_network' => false, + 'custom_internal_name' => 'my-custom-app', + 'is_container_label_escape_enabled' => true, + 'is_env_sorting_enabled' => true, + 'is_container_label_readonly_enabled' => false, + 'is_preserve_repository_enabled' => false, + 'disable_build_cache' => false, + 'is_spa' => true, + 'is_git_shallow_clone_enabled' => true, + 'is_pr_deployments_public_enabled' => false, + 'use_build_secrets' => false, + 'inject_build_args_to_dockerfile' => true, + 'include_source_commit_in_build' => true, + 'docker_images_to_keep' => 5, + ]); + + expect($setting->exists)->toBeTrue(); + expect($setting->application_id)->toBe($app->id); + expect($setting->is_static)->toBeTrue(); + expect($setting->is_gpu_enabled)->toBeTrue(); + expect($setting->gpu_driver)->toBe('nvidia'); + expect($setting->custom_internal_name)->toBe('my-custom-app'); + expect($setting->is_spa)->toBeTrue(); + expect($setting->docker_images_to_keep)->toBe(5); +}); + +it('creates ServerSetting with all fillable attributes', function () { + // Delete auto-created setting + ServerSetting::where('server_id', $this->server->id)->delete(); + + $setting = ServerSetting::create([ + 'server_id' => $this->server->id, + 'is_swarm_manager' => false, + 'is_jump_server' => false, + 'is_build_server' => true, + 'is_reachable' => true, + 'is_usable' => true, + 'wildcard_domain' => '*.example.com', + 'is_cloudflare_tunnel' => false, + 'is_logdrain_newrelic_enabled' => true, + 'logdrain_newrelic_license_key' => 'nr-license-key-123', + 'logdrain_newrelic_base_uri' => 'https://log-api.newrelic.com', + 'is_logdrain_highlight_enabled' => false, + 'logdrain_highlight_project_id' => 'hl-proj-123', + 'is_logdrain_axiom_enabled' => true, + 'logdrain_axiom_dataset_name' => 'coolify-logs', + 'logdrain_axiom_api_key' => 'axiom-key-456', + 'is_swarm_worker' => false, + 'is_logdrain_custom_enabled' => false, + 'logdrain_custom_config' => '{"endpoint":"https://logs.example.com"}', + 'logdrain_custom_config_parser' => 'json', + 'concurrent_builds' => 4, + 'dynamic_timeout' => 600, + 'force_disabled' => false, + 'is_metrics_enabled' => true, + 'generate_exact_labels' => true, + 'force_docker_cleanup' => false, + 'docker_cleanup_frequency' => '0 2 * * *', + 'docker_cleanup_threshold' => 80, + 'server_timezone' => 'UTC', + 'delete_unused_volumes' => true, + 'delete_unused_networks' => true, + 'is_sentinel_enabled' => true, + 'sentinel_token' => 'sentinel-token-789', + 'sentinel_metrics_refresh_rate_seconds' => 30, + 'sentinel_metrics_history_days' => 7, + 'sentinel_push_interval_seconds' => 60, + 'sentinel_custom_url' => 'https://sentinel.example.com', + 'server_disk_usage_notification_threshold' => 90, + 'is_sentinel_debug_enabled' => false, + 'server_disk_usage_check_frequency' => '*/5 * * * *', + 'is_terminal_enabled' => true, + 'deployment_queue_limit' => 10, + 'disable_application_image_retention' => false, + ]); + + expect($setting->exists)->toBeTrue(); + expect($setting->server_id)->toBe($this->server->id); + expect($setting->is_build_server)->toBeTrue(); + expect($setting->wildcard_domain)->toBe('*.example.com'); + expect($setting->concurrent_builds)->toBe(4); + expect($setting->sentinel_token)->toBe('sentinel-token-789'); + expect($setting->deployment_queue_limit)->toBe(10); +}); + +it('creates Service with all fillable attributes', function () { + $service = Service::create([ + 'uuid' => 'custom-service-uuid', + 'name' => 'Full Fillable Service', + 'description' => 'Service with all fillable attrs', + 'docker_compose_raw' => "services:\n app:\n image: nginx", + 'docker_compose' => "services:\n app:\n image: nginx", + 'connect_to_docker_network' => true, + 'service_type' => 'test-service', + 'config_hash' => 'sha256:svc123', + 'compose_parsing_version' => '2', + 'is_container_label_escape_enabled' => true, + 'environment_id' => $this->environment->id, + 'server_id' => $this->server->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + expect($service->exists)->toBeTrue(); + expect($service->uuid)->toBe('custom-service-uuid'); + expect($service->name)->toBe('Full Fillable Service'); + expect($service->docker_compose_raw)->not->toBeNull(); + expect($service->service_type)->toBe('test-service'); + expect($service->environment_id)->toBe($this->environment->id); + expect($service->server_id)->toBe($this->server->id); +}); + +it('creates ApplicationPreview with all fillable attributes', function () { + $app = Application::create([ + 'name' => 'preview-test-app', + 'git_repository' => 'https://github.com/test/repo', + 'git_branch' => 'main', + 'build_pack' => 'nixpacks', + 'ports_exposes' => '3000', + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $preview = ApplicationPreview::create([ + 'uuid' => 'custom-preview-uuid', + 'application_id' => $app->id, + 'pull_request_id' => 42, + 'pull_request_html_url' => 'https://github.com/test/repo/pull/42', + 'pull_request_issue_comment_id' => 12345, + 'fqdn' => 'https://pr-42.app.example.com', + 'status' => 'queued', + 'git_type' => 'github', + 'docker_compose_domains' => '{"app":"https://pr-42.example.com"}', + 'docker_registry_image_tag' => 'pr-42', + 'last_online_at' => now()->toISOString(), + ]); + + expect($preview->exists)->toBeTrue(); + expect($preview->uuid)->toBe('custom-preview-uuid'); + expect($preview->application_id)->toBe($app->id); + expect($preview->pull_request_id)->toBe(42); + expect($preview->fqdn)->toBe('https://pr-42.app.example.com'); + expect($preview->git_type)->toBe('github'); + expect($preview->docker_registry_image_tag)->toBe('pr-42'); +}); + +it('creates ServiceApplication with all fillable attributes', function () { + $service = Service::create([ + 'docker_compose_raw' => 'services: {}', + 'environment_id' => $this->environment->id, + 'server_id' => $this->server->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $svcApp = ServiceApplication::create([ + 'service_id' => $service->id, + 'name' => 'web', + 'human_name' => 'Web Server', + 'description' => 'Main web application', + 'fqdn' => 'https://web.example.com', + 'ports' => '80,443', + 'exposes' => '80', + 'status' => 'running', + 'exclude_from_status' => false, + 'required_fqdn' => true, + 'image' => 'nginx:latest', + 'is_log_drain_enabled' => true, + 'is_include_timestamps' => true, + 'is_gzip_enabled' => true, + 'is_stripprefix_enabled' => true, + 'last_online_at' => now()->toISOString(), + 'is_migrated' => false, + ]); + + expect($svcApp->exists)->toBeTrue(); + expect($svcApp->service_id)->toBe($service->id); + expect($svcApp->name)->toBe('web'); + expect($svcApp->human_name)->toBe('Web Server'); + expect($svcApp->image)->toBe('nginx:latest'); + expect($svcApp->is_log_drain_enabled)->toBeTrue(); +}); + +it('creates ServiceDatabase with all fillable attributes', function () { + $service = Service::create([ + 'docker_compose_raw' => 'services: {}', + 'environment_id' => $this->environment->id, + 'server_id' => $this->server->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $svcDb = ServiceDatabase::create([ + 'service_id' => $service->id, + 'name' => 'postgres', + 'human_name' => 'PostgreSQL', + 'description' => 'Main database', + 'ports' => '5432', + 'exposes' => '5432', + 'status' => 'running', + 'exclude_from_status' => false, + 'image' => 'postgres:16', + 'public_port' => 15432, + 'is_public' => true, + 'is_log_drain_enabled' => true, + 'is_include_timestamps' => true, + 'is_gzip_enabled' => false, + 'is_stripprefix_enabled' => false, + 'last_online_at' => now()->toISOString(), + 'is_migrated' => false, + 'custom_type' => 'postgresql', + 'public_port_timeout' => 3600, + ]); + + expect($svcDb->exists)->toBeTrue(); + expect($svcDb->service_id)->toBe($service->id); + expect($svcDb->name)->toBe('postgres'); + expect($svcDb->public_port)->toBe(15432); + expect($svcDb->is_public)->toBeTrue(); + expect($svcDb->custom_type)->toBe('postgresql'); +}); + +it('creates StandalonePostgresql with all fillable attributes', function () { + $db = StandalonePostgresql::create([ + 'uuid' => 'custom-pg-uuid', + 'name' => 'Full Fillable Postgres', + 'description' => 'PG with all attrs', + 'postgres_user' => 'testuser', + 'postgres_password' => 'testpass123', + 'postgres_db' => 'testdb', + 'postgres_initdb_args' => '--encoding=UTF8', + 'postgres_host_auth_method' => 'scram-sha-256', + 'postgres_conf' => 'max_connections=200', + 'init_scripts' => 'CREATE TABLE test (id int);', + 'status' => 'running', + 'image' => 'postgres:16-alpine', + 'is_public' => true, + 'public_port' => 25432, + 'ports_mappings' => '25432:5432', + 'limits_memory' => '1g', + 'limits_memory_swap' => '2g', + 'limits_memory_swappiness' => 50, + 'limits_memory_reservation' => '512m', + 'limits_cpus' => '2', + 'limits_cpuset' => '0-1', + 'limits_cpu_shares' => 1024, + 'started_at' => now()->subDay()->toISOString(), + 'restart_count' => 1, + 'last_restart_at' => now()->subHours(6)->toISOString(), + 'last_restart_type' => 'manual', + 'last_online_at' => now()->toISOString(), + 'public_port_timeout' => 7200, + 'enable_ssl' => true, + 'ssl_mode' => 'verify-full', + 'is_log_drain_enabled' => true, + 'is_include_timestamps' => true, + 'custom_docker_run_options' => '--shm-size=256m', + 'destination_type' => $this->destination->getMorphClass(), + 'destination_id' => $this->destination->id, + 'environment_id' => $this->environment->id, + ]); + + expect($db->exists)->toBeTrue(); + expect($db->uuid)->toBe('custom-pg-uuid'); + expect($db->postgres_user)->toBe('testuser'); + expect($db->postgres_db)->toBe('testdb'); + expect($db->is_public)->toBeTrue(); + expect($db->public_port)->toBe(25432); + expect($db->enable_ssl)->toBeTrue(); + expect($db->environment_id)->toBe($this->environment->id); +}); + +it('creates StandaloneMysql with all fillable attributes', function () { + $db = StandaloneMysql::create([ + 'uuid' => 'custom-mysql-uuid', + 'name' => 'Full Fillable MySQL', + 'description' => 'MySQL with all attrs', + 'mysql_root_password' => 'rootpass123', + 'mysql_user' => 'testuser', + 'mysql_password' => 'testpass123', + 'mysql_database' => 'testdb', + 'mysql_conf' => '[mysqld]\nmax_connections=200', + 'status' => 'running', + 'image' => 'mysql:8.0', + 'is_public' => false, + 'public_port' => 23306, + 'ports_mappings' => '23306:3306', + 'limits_memory' => '1g', + 'limits_memory_swap' => '2g', + 'limits_memory_swappiness' => 50, + 'limits_memory_reservation' => '512m', + 'limits_cpus' => '2', + 'limits_cpuset' => '0-1', + 'limits_cpu_shares' => 1024, + 'started_at' => now()->subDay()->toISOString(), + 'restart_count' => 0, + 'last_restart_at' => null, + 'last_restart_type' => null, + 'last_online_at' => now()->toISOString(), + 'public_port_timeout' => 3600, + 'enable_ssl' => true, + 'ssl_mode' => 'REQUIRED', + 'is_log_drain_enabled' => false, + 'is_include_timestamps' => false, + 'custom_docker_run_options' => '--ulimit nofile=65535:65535', + 'destination_type' => $this->destination->getMorphClass(), + 'destination_id' => $this->destination->id, + 'environment_id' => $this->environment->id, + ]); + + expect($db->exists)->toBeTrue(); + expect($db->uuid)->toBe('custom-mysql-uuid'); + expect($db->mysql_root_password)->toBe('rootpass123'); + expect($db->mysql_database)->toBe('testdb'); + expect($db->enable_ssl)->toBeTrue(); + expect($db->environment_id)->toBe($this->environment->id); +}); + +it('creates StandaloneMariadb with all fillable attributes', function () { + $db = StandaloneMariadb::create([ + 'uuid' => 'custom-maria-uuid', + 'name' => 'Full Fillable MariaDB', + 'description' => 'MariaDB with all attrs', + 'mariadb_root_password' => 'rootpass123', + 'mariadb_user' => 'testuser', + 'mariadb_password' => 'testpass123', + 'mariadb_database' => 'testdb', + 'mariadb_conf' => '[mysqld]\nmax_connections=200', + 'status' => 'running', + 'image' => 'mariadb:11', + 'is_public' => false, + 'public_port' => 23307, + 'ports_mappings' => '23307:3306', + 'limits_memory' => '1g', + 'limits_memory_swap' => '2g', + 'limits_memory_swappiness' => 50, + 'limits_memory_reservation' => '512m', + 'limits_cpus' => '2', + 'limits_cpuset' => '0-1', + 'limits_cpu_shares' => 1024, + 'started_at' => now()->subDay()->toISOString(), + 'restart_count' => 0, + 'last_restart_at' => null, + 'last_restart_type' => null, + 'last_online_at' => now()->toISOString(), + 'public_port_timeout' => 3600, + 'enable_ssl' => false, + 'is_log_drain_enabled' => false, + 'custom_docker_run_options' => '', + 'destination_type' => $this->destination->getMorphClass(), + 'destination_id' => $this->destination->id, + 'environment_id' => $this->environment->id, + ]); + + expect($db->exists)->toBeTrue(); + expect($db->uuid)->toBe('custom-maria-uuid'); + expect($db->mariadb_root_password)->toBe('rootpass123'); + expect($db->mariadb_database)->toBe('testdb'); + expect($db->environment_id)->toBe($this->environment->id); +}); + +it('creates StandaloneMongodb with all fillable attributes', function () { + $db = StandaloneMongodb::create([ + 'uuid' => 'custom-mongo-uuid', + 'name' => 'Full Fillable MongoDB', + 'description' => 'MongoDB with all attrs', + 'mongo_conf' => '{"storage":{"dbPath":"/data/db"}}', + 'mongo_initdb_root_username' => 'mongoadmin', + 'mongo_initdb_root_password' => 'mongopass123', + 'mongo_initdb_database' => 'testdb', + 'status' => 'running', + 'image' => 'mongo:7', + 'is_public' => false, + 'public_port' => 27018, + 'ports_mappings' => '27018:27017', + 'limits_memory' => '2g', + 'limits_memory_swap' => '4g', + 'limits_memory_swappiness' => 60, + 'limits_memory_reservation' => '1g', + 'limits_cpus' => '4', + 'limits_cpuset' => '0-3', + 'limits_cpu_shares' => 2048, + 'started_at' => now()->subDay()->toISOString(), + 'restart_count' => 0, + 'last_restart_at' => null, + 'last_restart_type' => null, + 'last_online_at' => now()->toISOString(), + 'public_port_timeout' => 3600, + 'enable_ssl' => false, + 'ssl_mode' => 'prefer', + 'is_log_drain_enabled' => false, + 'is_include_timestamps' => false, + 'custom_docker_run_options' => '', + 'destination_type' => $this->destination->getMorphClass(), + 'destination_id' => $this->destination->id, + 'environment_id' => $this->environment->id, + ]); + + expect($db->exists)->toBeTrue(); + expect($db->uuid)->toBe('custom-mongo-uuid'); + expect($db->mongo_initdb_root_username)->toBe('mongoadmin'); + expect($db->mongo_initdb_database)->toBe('testdb'); + expect($db->environment_id)->toBe($this->environment->id); +}); + +it('creates StandaloneRedis with all fillable attributes', function () { + $db = StandaloneRedis::create([ + 'uuid' => 'custom-redis-uuid', + 'name' => 'Full Fillable Redis', + 'description' => 'Redis with all attrs', + 'redis_conf' => 'maxmemory 256mb\nmaxmemory-policy allkeys-lru', + 'status' => 'running', + 'image' => 'redis:7-alpine', + 'is_public' => true, + 'public_port' => 26379, + 'ports_mappings' => '26379:6379', + 'limits_memory' => '512m', + 'limits_memory_swap' => '1g', + 'limits_memory_swappiness' => 30, + 'limits_memory_reservation' => '256m', + 'limits_cpus' => '1', + 'limits_cpuset' => '0', + 'limits_cpu_shares' => 512, + 'started_at' => now()->subDay()->toISOString(), + 'restart_count' => 0, + 'last_restart_at' => null, + 'last_restart_type' => null, + 'last_online_at' => now()->toISOString(), + 'public_port_timeout' => 3600, + 'enable_ssl' => false, + 'is_log_drain_enabled' => false, + 'is_include_timestamps' => false, + 'custom_docker_run_options' => '', + 'destination_type' => $this->destination->getMorphClass(), + 'destination_id' => $this->destination->id, + 'environment_id' => $this->environment->id, + ]); + + expect($db->exists)->toBeTrue(); + expect($db->uuid)->toBe('custom-redis-uuid'); + expect($db->redis_conf)->toContain('maxmemory'); + expect($db->is_public)->toBeTrue(); + expect($db->environment_id)->toBe($this->environment->id); +}); + +it('creates StandaloneKeydb with all fillable attributes', function () { + $db = StandaloneKeydb::create([ + 'uuid' => 'custom-keydb-uuid', + 'name' => 'Full Fillable KeyDB', + 'description' => 'KeyDB with all attrs', + 'keydb_password' => 'keydbpass123', + 'keydb_conf' => 'server-threads 4', + 'is_log_drain_enabled' => false, + 'is_include_timestamps' => false, + 'status' => 'running', + 'image' => 'eqalpha/keydb:latest', + 'is_public' => false, + 'public_port' => 26380, + 'ports_mappings' => '26380:6379', + 'limits_memory' => '512m', + 'limits_memory_swap' => '1g', + 'limits_memory_swappiness' => 30, + 'limits_memory_reservation' => '256m', + 'limits_cpus' => '2', + 'limits_cpuset' => '0-1', + 'limits_cpu_shares' => 512, + 'started_at' => now()->subDay()->toISOString(), + 'restart_count' => 0, + 'last_restart_at' => null, + 'last_restart_type' => null, + 'last_online_at' => now()->toISOString(), + 'public_port_timeout' => 3600, + 'enable_ssl' => false, + 'custom_docker_run_options' => '', + 'destination_type' => $this->destination->getMorphClass(), + 'destination_id' => $this->destination->id, + 'environment_id' => $this->environment->id, + ]); + + expect($db->exists)->toBeTrue(); + expect($db->uuid)->toBe('custom-keydb-uuid'); + expect($db->keydb_password)->toBe('keydbpass123'); + expect($db->environment_id)->toBe($this->environment->id); +}); + +it('creates StandaloneDragonfly with all fillable attributes', function () { + $db = StandaloneDragonfly::create([ + 'uuid' => 'custom-dragonfly-uuid', + 'name' => 'Full Fillable Dragonfly', + 'description' => 'Dragonfly with all attrs', + 'dragonfly_password' => 'dragonflypass123', + 'is_log_drain_enabled' => false, + 'is_include_timestamps' => false, + 'status' => 'running', + 'image' => 'docker.dragonflydb.io/dragonflydb/dragonfly:latest', + 'is_public' => false, + 'public_port' => 26381, + 'ports_mappings' => '26381:6379', + 'limits_memory' => '1g', + 'limits_memory_swap' => '2g', + 'limits_memory_swappiness' => 30, + 'limits_memory_reservation' => '512m', + 'limits_cpus' => '2', + 'limits_cpuset' => '0-1', + 'limits_cpu_shares' => 512, + 'started_at' => now()->subDay()->toISOString(), + 'restart_count' => 0, + 'last_restart_at' => null, + 'last_restart_type' => null, + 'last_online_at' => now()->toISOString(), + 'public_port_timeout' => 3600, + 'enable_ssl' => false, + 'custom_docker_run_options' => '', + 'destination_type' => $this->destination->getMorphClass(), + 'destination_id' => $this->destination->id, + 'environment_id' => $this->environment->id, + ]); + + expect($db->exists)->toBeTrue(); + expect($db->uuid)->toBe('custom-dragonfly-uuid'); + expect($db->dragonfly_password)->toBe('dragonflypass123'); + expect($db->environment_id)->toBe($this->environment->id); +}); + +it('creates StandaloneClickhouse with all fillable attributes', function () { + $db = StandaloneClickhouse::create([ + 'uuid' => 'custom-ch-uuid', + 'name' => 'Full Fillable ClickHouse', + 'description' => 'ClickHouse with all attrs', + 'clickhouse_admin_user' => 'chadmin', + 'clickhouse_admin_password' => 'chpass123', + 'is_log_drain_enabled' => false, + 'is_include_timestamps' => false, + 'status' => 'running', + 'image' => 'clickhouse/clickhouse-server:latest', + 'is_public' => false, + 'public_port' => 28123, + 'ports_mappings' => '28123:8123', + 'limits_memory' => '2g', + 'limits_memory_swap' => '4g', + 'limits_memory_swappiness' => 30, + 'limits_memory_reservation' => '1g', + 'limits_cpus' => '4', + 'limits_cpuset' => '0-3', + 'limits_cpu_shares' => 2048, + 'started_at' => now()->subDay()->toISOString(), + 'restart_count' => 0, + 'last_restart_at' => null, + 'last_restart_type' => null, + 'last_online_at' => now()->toISOString(), + 'public_port_timeout' => 3600, + 'custom_docker_run_options' => '', + 'clickhouse_db' => 'testdb', + 'destination_type' => $this->destination->getMorphClass(), + 'destination_id' => $this->destination->id, + 'environment_id' => $this->environment->id, + ]); + + expect($db->exists)->toBeTrue(); + expect($db->uuid)->toBe('custom-ch-uuid'); + expect($db->clickhouse_admin_user)->toBe('chadmin'); + expect($db->clickhouse_db)->toBe('testdb'); + expect($db->environment_id)->toBe($this->environment->id); +}); + +it('creates SwarmDocker with all fillable attributes', function () { + $swarm = SwarmDocker::create([ + 'server_id' => $this->server->id, + 'name' => 'swarm-dest', + 'network' => 'coolify-swarm', + ]); + + expect($swarm->exists)->toBeTrue(); + expect($swarm->server_id)->toBe($this->server->id); + expect($swarm->name)->toBe('swarm-dest'); + expect($swarm->network)->toBe('coolify-swarm'); +}); + +it('creates StandaloneDocker with all fillable attributes', function () { + $docker = StandaloneDocker::create([ + 'server_id' => $this->server->id, + 'name' => 'standalone-dest', + 'network' => 'coolify-standalone', + ]); + + expect($docker->exists)->toBeTrue(); + expect($docker->server_id)->toBe($this->server->id); + expect($docker->name)->toBe('standalone-dest'); + expect($docker->network)->toBe('coolify-standalone'); +}); + +it('creates ScheduledTask with all fillable attributes', function () { + $app = Application::create([ + 'name' => 'task-test-app', + 'git_repository' => 'https://github.com/test/repo', + 'git_branch' => 'main', + 'build_pack' => 'nixpacks', + 'ports_exposes' => '3000', + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $task = ScheduledTask::create([ + 'uuid' => 'custom-task-uuid', + 'enabled' => true, + 'name' => 'Full Fillable Task', + 'command' => 'php artisan schedule:run', + 'frequency' => '* * * * *', + 'container' => 'app', + 'timeout' => 300, + 'team_id' => $this->team->id, + 'application_id' => $app->id, + 'service_id' => null, + ]); + + expect($task->exists)->toBeTrue(); + expect($task->uuid)->toBe('custom-task-uuid'); + expect($task->name)->toBe('Full Fillable Task'); + expect($task->command)->toBe('php artisan schedule:run'); + expect($task->frequency)->toBe('* * * * *'); + expect($task->container)->toBe('app'); + expect($task->timeout)->toBe(300); + expect($task->team_id)->toBe($this->team->id); + expect($task->application_id)->toBe($app->id); +}); + +it('creates ScheduledDatabaseBackup with all fillable attributes', function () { + $db = StandalonePostgresql::create([ + 'name' => 'backup-test-pg', + 'postgres_user' => 'user', + 'postgres_password' => 'pass', + 'postgres_db' => 'testdb', + 'destination_type' => $this->destination->getMorphClass(), + 'destination_id' => $this->destination->id, + 'environment_id' => $this->environment->id, + ]); + + $backup = ScheduledDatabaseBackup::create([ + 'uuid' => 'custom-backup-uuid', + 'team_id' => $this->team->id, + 'description' => 'Full fillable backup', + 'enabled' => true, + 'save_s3' => false, + 'frequency' => '0 2 * * *', + 'database_backup_retention_amount_locally' => 10, + 'database_type' => $db->getMorphClass(), + 'database_id' => $db->id, + 's3_storage_id' => null, + 'databases_to_backup' => 'testdb', + 'dump_all' => false, + 'database_backup_retention_days_locally' => 30, + 'database_backup_retention_max_storage_locally' => 5000, + 'database_backup_retention_amount_s3' => 20, + 'database_backup_retention_days_s3' => 60, + 'database_backup_retention_max_storage_s3' => 10000, + 'timeout' => 600, + 'disable_local_backup' => false, + ]); + + expect($backup->exists)->toBeTrue(); + expect($backup->uuid)->toBe('custom-backup-uuid'); + expect($backup->frequency)->toBe('0 2 * * *'); + expect($backup->database_backup_retention_amount_locally)->toBe(10); + expect($backup->databases_to_backup)->toBe('testdb'); + expect($backup->timeout)->toBe(600); +}); + +it('creates ScheduledDatabaseBackupExecution with all fillable attributes', function () { + $db = StandalonePostgresql::create([ + 'name' => 'exec-test-pg', + 'postgres_user' => 'user', + 'postgres_password' => 'pass', + 'postgres_db' => 'testdb', + 'destination_type' => $this->destination->getMorphClass(), + 'destination_id' => $this->destination->id, + 'environment_id' => $this->environment->id, + ]); + $backup = ScheduledDatabaseBackup::create([ + 'frequency' => '0 2 * * *', + 'database_type' => $db->getMorphClass(), + 'database_id' => $db->id, + 'team_id' => $this->team->id, + ]); + + $execution = ScheduledDatabaseBackupExecution::create([ + 'uuid' => 'custom-exec-uuid', + 'scheduled_database_backup_id' => $backup->id, + 'status' => 'success', + 'message' => 'Backup completed successfully', + 'size' => 1048576, + 'filename' => 'backup-2026-03-31.sql.gz', + 'database_name' => 'testdb', + 'finished_at' => now()->toISOString(), + 'local_storage_deleted' => false, + 's3_storage_deleted' => false, + 's3_uploaded' => false, + ]); + + expect($execution->exists)->toBeTrue(); + expect($execution->uuid)->toBe('custom-exec-uuid'); + expect($execution->status)->toBe('success'); + expect($execution->filename)->toBe('backup-2026-03-31.sql.gz'); + expect($execution->database_name)->toBe('testdb'); + expect($execution->size)->toBe(1048576); +}); + +it('creates ScheduledTaskExecution with all fillable attributes', function () { + $app = Application::create([ + 'name' => 'task-exec-app', + 'git_repository' => 'https://github.com/test/repo', + 'git_branch' => 'main', + 'build_pack' => 'nixpacks', + 'ports_exposes' => '3000', + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + $task = ScheduledTask::create([ + 'name' => 'exec-test-task', + 'command' => 'echo hello', + 'frequency' => '* * * * *', + 'timeout' => 60, + 'team_id' => $this->team->id, + 'application_id' => $app->id, + ]); + + $execution = ScheduledTaskExecution::create([ + 'scheduled_task_id' => $task->id, + 'status' => 'success', + 'message' => 'Task completed successfully', + 'finished_at' => now()->toISOString(), + 'started_at' => now()->subMinute()->toISOString(), + 'retry_count' => 0, + 'duration' => 60, + 'error_details' => null, + ]); + + expect($execution->exists)->toBeTrue(); + expect($execution->scheduled_task_id)->toBe($task->id); + expect($execution->status)->toBe('success'); + expect((float) $execution->duration)->toBe(60.0); + expect($execution->retry_count)->toBe(0); +}); + +it('creates GithubApp with all fillable attributes', function () { + $githubApp = GithubApp::create([ + 'team_id' => $this->team->id, + 'private_key_id' => $this->server->private_key_id, + 'name' => 'Full Fillable GH App', + 'organization' => 'coollabsio', + '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' => 'Iv1.abc123', + 'client_secret' => 'secret-456', + 'webhook_secret' => 'whsec-789', + 'is_system_wide' => false, + 'is_public' => false, + 'contents' => 'read', + 'metadata' => 'read', + 'pull_requests' => 'write', + 'administration' => 'read', + ]); + + expect($githubApp->exists)->toBeTrue(); + expect($githubApp->name)->toBe('Full Fillable GH App'); + expect($githubApp->organization)->toBe('coollabsio'); + expect($githubApp->app_id)->toBe(12345); + expect($githubApp->installation_id)->toBe(67890); + expect($githubApp->client_id)->toBe('Iv1.abc123'); + expect($githubApp->team_id)->toBe($this->team->id); + expect($githubApp->private_key_id)->toBe($this->server->private_key_id); +}); + +it('creates Subscription with all fillable attributes', function () { + $sub = Subscription::create([ + 'team_id' => $this->team->id, + 'stripe_invoice_paid' => true, + 'stripe_subscription_id' => 'sub_1234567890', + 'stripe_customer_id' => 'cus_1234567890', + 'stripe_cancel_at_period_end' => false, + 'stripe_plan_id' => 'price_1234567890', + 'stripe_feedback' => 'Great service', + 'stripe_comment' => 'Will renew', + 'stripe_trial_already_ended' => true, + 'stripe_past_due' => false, + 'stripe_refunded_at' => null, + ]); + + expect($sub->exists)->toBeTrue(); + expect($sub->team_id)->toBe($this->team->id); + expect($sub->stripe_subscription_id)->toBe('sub_1234567890'); + expect($sub->stripe_customer_id)->toBe('cus_1234567890'); + expect($sub->stripe_plan_id)->toBe('price_1234567890'); + expect($sub->stripe_invoice_paid)->toBeTrue(); +}); + +it('creates CloudProviderToken with all fillable attributes', function () { + $token = CloudProviderToken::create([ + 'team_id' => $this->team->id, + 'provider' => 'hetzner', + 'token' => 'hcloud-token-abc123', + 'name' => 'My Hetzner Token', + ]); + + expect($token->exists)->toBeTrue(); + expect($token->team_id)->toBe($this->team->id); + expect($token->provider)->toBe('hetzner'); + expect($token->token)->toBe('hcloud-token-abc123'); + expect($token->name)->toBe('My Hetzner Token'); +}); + +it('creates Tag with all fillable attributes', function () { + $tag = Tag::create([ + 'name' => 'production', + 'team_id' => $this->team->id, + ]); + + expect($tag->exists)->toBeTrue(); + expect($tag->name)->toBe('production'); + expect($tag->team_id)->toBe($this->team->id); +}); diff --git a/tests/Feature/ServiceDatabaseTeamTest.php b/tests/Feature/ServiceDatabaseTeamTest.php index ae3cba4d3..5fe7e39d2 100644 --- a/tests/Feature/ServiceDatabaseTeamTest.php +++ b/tests/Feature/ServiceDatabaseTeamTest.php @@ -14,18 +14,18 @@ it('returns the correct team through the service relationship chain', function () { $team = Team::factory()->create(); - $project = Project::forceCreate([ + $project = Project::create([ 'uuid' => (string) Str::uuid(), 'name' => 'Test Project', 'team_id' => $team->id, ]); - $environment = Environment::forceCreate([ + $environment = Environment::create([ 'name' => 'test-env-'.Str::random(8), 'project_id' => $project->id, ]); - $service = Service::forceCreate([ + $service = Service::create([ 'uuid' => (string) Str::uuid(), 'name' => 'supabase', 'environment_id' => $environment->id, @@ -34,7 +34,7 @@ 'docker_compose_raw' => 'version: "3"', ]); - $serviceDatabase = ServiceDatabase::forceCreate([ + $serviceDatabase = ServiceDatabase::create([ 'uuid' => (string) Str::uuid(), 'name' => 'supabase-db', 'service_id' => $service->id, @@ -47,18 +47,18 @@ it('returns the correct team for ServiceApplication through the service relationship chain', function () { $team = Team::factory()->create(); - $project = Project::forceCreate([ + $project = Project::create([ 'uuid' => (string) Str::uuid(), 'name' => 'Test Project', 'team_id' => $team->id, ]); - $environment = Environment::forceCreate([ + $environment = Environment::create([ 'name' => 'test-env-'.Str::random(8), 'project_id' => $project->id, ]); - $service = Service::forceCreate([ + $service = Service::create([ 'uuid' => (string) Str::uuid(), 'name' => 'supabase', 'environment_id' => $environment->id, @@ -67,7 +67,7 @@ 'docker_compose_raw' => 'version: "3"', ]); - $serviceApplication = ServiceApplication::forceCreate([ + $serviceApplication = ServiceApplication::create([ 'uuid' => (string) Str::uuid(), 'name' => 'supabase-studio', 'service_id' => $service->id, diff --git a/tests/Feature/StorageApiTest.php b/tests/Feature/StorageApiTest.php index bd9d727c4..75357e41e 100644 --- a/tests/Feature/StorageApiTest.php +++ b/tests/Feature/StorageApiTest.php @@ -49,7 +49,7 @@ function createTestApplication($context): Application function createTestDatabase($context): StandalonePostgresql { - return StandalonePostgresql::forceCreate([ + return StandalonePostgresql::create([ 'name' => 'test-postgres', 'image' => 'postgres:15-alpine', 'postgres_user' => 'postgres', diff --git a/tests/Unit/GitRefValidationTest.php b/tests/Unit/GitRefValidationTest.php index 58d07f4b7..f82dcb863 100644 --- a/tests/Unit/GitRefValidationTest.php +++ b/tests/Unit/GitRefValidationTest.php @@ -1,12 +1,14 @@ toBe('abc123def456'); @@ -93,31 +95,31 @@ describe('executeInDocker git log escaping', function () { test('git log command escapes commit SHA to prevent injection', function () { $maliciousCommit = "HEAD'; id; #"; - $command = "cd /workdir && git log -1 ".escapeshellarg($maliciousCommit).' --pretty=%B'; + $command = 'cd /workdir && git log -1 '.escapeshellarg($maliciousCommit).' --pretty=%B'; $result = executeInDocker('test-container', $command); // The malicious payload must not be able to break out of quoting - expect($result)->not->toContain("id;"); + expect($result)->not->toContain('id;'); expect($result)->toContain("'HEAD'\\''"); }); }); describe('buildGitCheckoutCommand escaping', function () { test('checkout command escapes target to prevent injection', function () { - $app = new \App\Models\Application; - $app->forceFill(['uuid' => 'test-uuid']); + $app = new Application; + $app->fill(['uuid' => 'test-uuid']); - $settings = new \App\Models\ApplicationSetting; + $settings = new ApplicationSetting; $settings->is_git_submodules_enabled = false; $app->setRelation('settings', $settings); - $method = new \ReflectionMethod($app, 'buildGitCheckoutCommand'); + $method = new ReflectionMethod($app, 'buildGitCheckoutCommand'); $result = $method->invoke($app, 'abc123'); expect($result)->toContain("git checkout 'abc123'"); $result = $method->invoke($app, "abc'; id; #"); - expect($result)->not->toContain("id;"); + expect($result)->not->toContain('id;'); expect($result)->toContain("git checkout 'abc'"); }); }); diff --git a/tests/Unit/ModelFillableRegressionTest.php b/tests/Unit/ModelFillableRegressionTest.php new file mode 100644 index 000000000..eff477c5a --- /dev/null +++ b/tests/Unit/ModelFillableRegressionTest.php @@ -0,0 +1,76 @@ +getFillable())->toContain(...$expectedAttributes); +})->with([ + // Relationship/ownership keys + [CloudProviderToken::class, ['team_id']], + [Tag::class, ['team_id']], + [Subscription::class, ['team_id']], + [ScheduledTaskExecution::class, ['scheduled_task_id']], + [ScheduledDatabaseBackupExecution::class, ['uuid', 'scheduled_database_backup_id']], + [ScheduledDatabaseBackup::class, ['uuid', 'team_id']], + [ScheduledTask::class, ['uuid', 'team_id', 'application_id', 'service_id']], + [ServiceDatabase::class, ['service_id']], + [ServiceApplication::class, ['service_id']], + [ApplicationDeploymentQueue::class, ['docker_registry_image_tag']], + [Project::class, ['team_id', 'uuid']], + [Environment::class, ['project_id', 'uuid']], + [ProjectSetting::class, ['project_id']], + [ApplicationSetting::class, ['application_id']], + [ServerSetting::class, ['server_id']], + [SwarmDocker::class, ['server_id']], + [StandaloneDocker::class, ['server_id']], + [User::class, ['pending_email', 'email_change_code', 'email_change_code_expires_at']], + [Server::class, ['ip_previous']], + [GithubApp::class, ['team_id', 'private_key_id']], + + // Application/Service resource keys (including uuid for clone flows) + [Application::class, ['uuid', 'environment_id', 'destination_id', 'destination_type', 'source_id', 'source_type', 'repository_project_id', 'private_key_id']], + [ApplicationPreview::class, ['uuid', 'application_id']], + [Service::class, ['uuid', 'environment_id', 'server_id', 'destination_id', 'destination_type']], + + // Standalone database resource keys (including uuid for clone flows) + [StandalonePostgresql::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']], + [StandaloneMysql::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']], + [StandaloneMariadb::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']], + [StandaloneMongodb::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']], + [StandaloneRedis::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']], + [StandaloneKeydb::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']], + [StandaloneDragonfly::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']], + [StandaloneClickhouse::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']], +]); diff --git a/tests/Unit/ServiceParserImageUpdateTest.php b/tests/Unit/ServiceParserImageUpdateTest.php index 649795866..f672b64f5 100644 --- a/tests/Unit/ServiceParserImageUpdateTest.php +++ b/tests/Unit/ServiceParserImageUpdateTest.php @@ -16,8 +16,8 @@ expect($parsersFile) ->toContain("\$databaseFound = ServiceDatabase::where('name', \$serviceName)->where('service_id', \$resource->id)->first();") ->toContain("\$applicationFound = ServiceApplication::where('name', \$serviceName)->where('service_id', \$resource->id)->first();") - ->toContain("forceCreate([\n 'name' => \$serviceName,\n 'service_id' => \$resource->id,\n ]);") - ->not->toContain("forceCreate([\n 'name' => \$serviceName,\n 'image' => \$image,\n 'service_id' => \$resource->id,\n ]);"); + ->toContain("create([\n 'name' => \$serviceName,\n 'service_id' => \$resource->id,\n ]);") + ->not->toContain("create([\n 'name' => \$serviceName,\n 'image' => \$image,\n 'service_id' => \$resource->id,\n ]);"); }); it('ensures service parser updates image after finding or creating service', function () { @@ -41,8 +41,8 @@ // The new code checks for null within the else block and creates only if needed expect($sharedFile) ->toContain('if (is_null($savedService)) {') - ->toContain('$savedService = ServiceDatabase::forceCreate([') - ->toContain('$savedService = ServiceApplication::forceCreate(['); + ->toContain('$savedService = ServiceDatabase::create([') + ->toContain('$savedService = ServiceApplication::create(['); }); it('verifies image update logic is present in parseDockerComposeFile', function () { diff --git a/tests/v4/Browser/DashboardTest.php b/tests/v4/Browser/DashboardTest.php index 233b0db9d..b4a97f268 100644 --- a/tests/v4/Browser/DashboardTest.php +++ b/tests/v4/Browser/DashboardTest.php @@ -77,21 +77,21 @@ ], ]); - Project::forceCreate([ + Project::create([ 'uuid' => 'project-1', 'name' => 'My first project', 'description' => 'This is a test project in development', 'team_id' => 0, ]); - Project::forceCreate([ + Project::create([ 'uuid' => 'project-2', 'name' => 'Production API', 'description' => 'Backend services for production', 'team_id' => 0, ]); - Project::forceCreate([ + Project::create([ 'uuid' => 'project-3', 'name' => 'Staging Environment', 'description' => 'Staging and QA testing', From a77e1f47d1af81724d26bbaf57870deac730ec26 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 31 Mar 2026 13:50:37 +0200 Subject: [PATCH 4/4] fix(models): replace forceCreate with forceFill+save pattern Replaces Model::forceCreate([...]) calls with (new Model)->forceFill([...])->save() across SettingsBackup, Server, and User models to avoid bypassing Eloquent model event lifecycle during record creation. --- app/Livewire/SettingsBackup.php | 4 +++- app/Models/Server.php | 12 ++++++------ app/Models/User.php | 6 ++++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/Livewire/SettingsBackup.php b/app/Livewire/SettingsBackup.php index a111a6096..5336c0c9a 100644 --- a/app/Livewire/SettingsBackup.php +++ b/app/Livewire/SettingsBackup.php @@ -83,7 +83,8 @@ public function addCoolifyDatabase() $postgres_password = $envs['POSTGRES_PASSWORD']; $postgres_user = $envs['POSTGRES_USER']; $postgres_db = $envs['POSTGRES_DB']; - $this->database = StandalonePostgresql::forceCreate([ + $this->database = new StandalonePostgresql; + $this->database->forceFill([ 'id' => 0, 'name' => 'coolify-db', 'description' => 'Coolify database', @@ -94,6 +95,7 @@ public function addCoolifyDatabase() 'destination_type' => StandaloneDocker::class, 'destination_id' => 0, ]); + $this->database->save(); $this->backup = ScheduledDatabaseBackup::create([ 'id' => 0, 'enabled' => true, diff --git a/app/Models/Server.php b/app/Models/Server.php index a18fe14ae..918b44270 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -143,28 +143,28 @@ protected static function booted() } }); static::created(function ($server) { - ServerSetting::forceCreate([ + ServerSetting::create([ 'server_id' => $server->id, ]); if ($server->id === 0) { if ($server->isSwarm()) { - SwarmDocker::forceCreate([ + (new SwarmDocker)->forceFill([ 'id' => 0, 'name' => 'coolify', 'network' => 'coolify-overlay', 'server_id' => $server->id, - ]); + ])->save(); } else { - StandaloneDocker::forceCreate([ + (new StandaloneDocker)->forceFill([ 'id' => 0, 'name' => 'coolify', 'network' => 'coolify', 'server_id' => $server->id, - ]); + ])->saveQuietly(); } } else { if ($server->isSwarm()) { - SwarmDocker::forceCreate([ + SwarmDocker::create([ 'name' => 'coolify-overlay', 'network' => 'coolify-overlay', 'server_id' => $server->id, diff --git a/app/Models/User.php b/app/Models/User.php index aa33a49fb..3199d2024 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -98,7 +98,8 @@ protected static function boot() $team['id'] = 0; $team['name'] = 'Root Team'; } - $new_team = Team::forceCreate($team); + $new_team = (new Team)->forceFill($team); + $new_team->save(); $user->teams()->attach($new_team, ['role' => 'owner']); }); @@ -201,7 +202,8 @@ public function recreate_personal_team() $team['id'] = 0; $team['name'] = 'Root Team'; } - $new_team = Team::forceCreate($team); + $new_team = (new Team)->forceFill($team); + $new_team->save(); $this->teams()->attach($new_team, ['role' => 'owner']); return $new_team;