From 96ef0ef749bd31fdbd56241956d4161ebc9b8b77 Mon Sep 17 00:00:00 2001 From: Danilo Martinelli Date: Sun, 29 Sep 2024 13:51:41 -0300 Subject: [PATCH 01/84] feat: :passport_control: integrate Authentik authentication with Coolify - Configured Authentik as the OAuth provider in Coolify. --- app/Livewire/SettingsOauth.php | 1 + app/Providers/EventServiceProvider.php | 1 + bootstrap/helpers/socialite.php | 11 ++++ composer.json | 1 + composer.lock | 50 +++++++++++++++++++ config/services.php | 7 +++ ...5_add_base_url_to_oauth_settings_table.php | 32 ++++++++++++ database/seeders/OauthSettingSeeder.php | 4 ++ lang/en.json | 1 + .../views/livewire/settings-oauth.blade.php | 4 ++ 10 files changed, 112 insertions(+) create mode 100644 database/migrations/2024_09_29_163625_add_base_url_to_oauth_settings_table.php diff --git a/app/Livewire/SettingsOauth.php b/app/Livewire/SettingsOauth.php index c3884589f..472d35ba0 100644 --- a/app/Livewire/SettingsOauth.php +++ b/app/Livewire/SettingsOauth.php @@ -17,6 +17,7 @@ class SettingsOauth extends Component $carry["oauth_settings_map.$setting->provider.client_secret"] = 'nullable'; $carry["oauth_settings_map.$setting->provider.redirect_uri"] = 'nullable'; $carry["oauth_settings_map.$setting->provider.tenant"] = 'nullable'; + $carry["oauth_settings_map.$setting->provider.base_url"] = 'nullable'; return $carry; }, []); diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 7ba72e10d..2147495fa 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -21,6 +21,7 @@ class EventServiceProvider extends ServiceProvider ], \SocialiteProviders\Manager\SocialiteWasCalled::class => [ \SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle', + \SocialiteProviders\Authentik\AuthentikExtendSocialite::class.'@handle', ], ProxyStarted::class => [ ProxyStartedNotification::class, diff --git a/bootstrap/helpers/socialite.php b/bootstrap/helpers/socialite.php index a23dc24d3..2922f5a00 100644 --- a/bootstrap/helpers/socialite.php +++ b/bootstrap/helpers/socialite.php @@ -18,6 +18,17 @@ function get_socialite_provider(string $provider) return Socialite::driver('azure')->setConfig($azure_config); } + if ($provider == 'authentik') { + $authentik_config = new \SocialiteProviders\Manager\Config( + $oauth_setting->client_id, + $oauth_setting->client_secret, + $oauth_setting->redirect_uri, + ['base_url' => $oauth_setting->base_url], + ); + + return Socialite::driver('authentik')->setConfig($authentik_config); + } + $config = [ 'client_id' => $oauth_setting->client_id, 'client_secret' => $oauth_setting->client_secret, diff --git a/composer.json b/composer.json index fbd77d0cf..8763d1b07 100644 --- a/composer.json +++ b/composer.json @@ -35,6 +35,7 @@ "pusher/pusher-php-server": "^7.2", "resend/resend-laravel": "^0.13.0", "sentry/sentry-laravel": "^4.6", + "socialiteproviders/authentik": "^5.2", "socialiteproviders/microsoft-azure": "^5.1", "spatie/laravel-activitylog": "^4.7.3", "spatie/laravel-data": "^3.4.3", diff --git a/composer.lock b/composer.lock index 0b8da82d0..04c4741ba 100644 --- a/composer.lock +++ b/composer.lock @@ -7586,6 +7586,56 @@ ], "time": "2024-09-19T12:58:53+00:00" }, + { + "name": "socialiteproviders/authentik", + "version": "5.2.0", + "source": { + "type": "git", + "url": "https://github.com/SocialiteProviders/Authentik.git", + "reference": "4cf129cf04728a38e0531c54454464b162f0fa66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SocialiteProviders/Authentik/zipball/4cf129cf04728a38e0531c54454464b162f0fa66", + "reference": "4cf129cf04728a38e0531c54454464b162f0fa66", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.0", + "socialiteproviders/manager": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "SocialiteProviders\\Authentik\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "rf152", + "email": "git@rf152.co.uk" + } + ], + "description": "Authentik OAuth2 Provider for Laravel Socialite", + "keywords": [ + "authentik", + "laravel", + "oauth", + "provider", + "socialite" + ], + "support": { + "docs": "https://socialiteproviders.com/authentik", + "issues": "https://github.com/socialiteproviders/providers/issues", + "source": "https://github.com/socialiteproviders/providers" + }, + "time": "2023-11-07T22:21:16+00:00" + }, { "name": "socialiteproviders/manager", "version": "v4.6.0", diff --git a/config/services.php b/config/services.php index 9fd55870f..46fd12ec3 100644 --- a/config/services.php +++ b/config/services.php @@ -38,4 +38,11 @@ return [ 'tenant' => env('AZURE_TENANT_ID'), 'proxy' => env('AZURE_PROXY'), ], + + 'authentik' => [ + 'base_url' => env('AUTHENTIK_BASE_URL'), + 'client_id' => env('AUTHENTIK_CLIENT_ID'), + 'client_secret' => env('AUTHENTIK_CLIENT_SECRET'), + 'redirect' => env('AUTHENTIK_REDIRECT_URI'), + ], ]; diff --git a/database/migrations/2024_09_29_163625_add_base_url_to_oauth_settings_table.php b/database/migrations/2024_09_29_163625_add_base_url_to_oauth_settings_table.php new file mode 100644 index 000000000..8df9b48a2 --- /dev/null +++ b/database/migrations/2024_09_29_163625_add_base_url_to_oauth_settings_table.php @@ -0,0 +1,32 @@ +string('base_url')->nullable(); + }); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('oauth_settings', function (Blueprint $table) { + Schema::table('oauth_settings', function (Blueprint $table) { + $table->dropColumn('base_url'); + }); + }); + } +}; diff --git a/database/seeders/OauthSettingSeeder.php b/database/seeders/OauthSettingSeeder.php index 16abf9e04..d0365c542 100644 --- a/database/seeders/OauthSettingSeeder.php +++ b/database/seeders/OauthSettingSeeder.php @@ -32,5 +32,9 @@ class OauthSettingSeeder extends Seeder 'id' => 4, 'provider' => 'google', ]); + OauthSetting::firstOrCreate([ + 'id' => 5, + 'provider' => 'authentik', + ]); } } diff --git a/lang/en.json b/lang/en.json index fa69c7035..383bbda02 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1,5 +1,6 @@ { "auth.login": "Login", + "auth.login.authentik": "Login with Authentik", "auth.login.azure": "Login with Microsoft", "auth.login.bitbucket": "Login with Bitbucket", "auth.login.github": "Login with GitHub", diff --git a/resources/views/livewire/settings-oauth.blade.php b/resources/views/livewire/settings-oauth.blade.php index 9a94d3c2b..2362ad880 100644 --- a/resources/views/livewire/settings-oauth.blade.php +++ b/resources/views/livewire/settings-oauth.blade.php @@ -32,6 +32,10 @@ @endif + @if ($oauth_setting->provider == 'authentik') + + @endif @endforeach From 46ba869c413712a9daf8fa1b1fde3896bc6c1b13 Mon Sep 17 00:00:00 2001 From: dre-on Date: Mon, 9 Dec 2024 20:12:55 -0500 Subject: [PATCH 02/84] Adds template for Sonatype Nexus service --- public/svgs/nexus-arm.png | Bin 0 -> 6698 bytes public/svgs/nexus.png | Bin 0 -> 6698 bytes templates/compose/nexus-arm.yaml | 22 ++++++++++++++++++++++ templates/compose/nexus.yaml | 22 ++++++++++++++++++++++ 4 files changed, 44 insertions(+) create mode 100644 public/svgs/nexus-arm.png create mode 100644 public/svgs/nexus.png create mode 100644 templates/compose/nexus-arm.yaml create mode 100644 templates/compose/nexus.yaml diff --git a/public/svgs/nexus-arm.png b/public/svgs/nexus-arm.png new file mode 100644 index 0000000000000000000000000000000000000000..823ea4e867586dca08782ea294a33437302d8ab9 GIT binary patch literal 6698 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 VDob)N1ONO1ZeG*Vs=8_u_8*&hZtDO5 literal 0 HcmV?d00001 diff --git a/public/svgs/nexus.png b/public/svgs/nexus.png new file mode 100644 index 0000000000000000000000000000000000000000..823ea4e867586dca08782ea294a33437302d8ab9 GIT binary patch literal 6698 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 VDob)N1ONO1ZeG*Vs=8_u_8*&hZtDO5 literal 0 HcmV?d00001 diff --git a/templates/compose/nexus-arm.yaml b/templates/compose/nexus-arm.yaml new file mode 100644 index 000000000..d76578adc --- /dev/null +++ b/templates/compose/nexus-arm.yaml @@ -0,0 +1,22 @@ +# documentation: https://help.sonatype.com/en/sonatype-nexus-repository.html +# slogan: Open source Universal Repository Manager (ARM version, community edition), default credentials: admin/admin123 +# tags: repository, manager, open source, docker, docker, registry, container +# logo: svgs/nexus-arm.png +# port: 8081 + +services: + nexus: + image: sonatypecommunity/nexus3 + restart: on-failure + environment: + - SERVICE_FQDN_NEXUS_8081 + - NEXUS_SECURITY_RANDOMPASSWORD=false + - INSTALL4J_ADD_VM_PARAMS=-Xms2703m -Xmx2703m -XX:MaxDirectMemorySize=2703m -Djava.util.prefs.userRoot=/nexus-data/javaprefs + volumes: + - nexus_data:/nexus-data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8081/service/rest/v1/status"] + interval: 10s + timeout: 10s + retries: 3 + start_period: 60s diff --git a/templates/compose/nexus.yaml b/templates/compose/nexus.yaml new file mode 100644 index 000000000..de9f93075 --- /dev/null +++ b/templates/compose/nexus.yaml @@ -0,0 +1,22 @@ +# documentation: https://help.sonatype.com/en/sonatype-nexus-repository.html +# slogan: Open source Universal Repository Manager (x86_64 version, official), default credentials: admin/admin123 +# tags: repository, manager, open source, docker, docker, registry, container +# logo: svgs/nexus.png +# port: 8081 + +services: + nexus: + image: sonatype/nexus3 + restart: on-failure + environment: + - SERVICE_FQDN_NEXUS_8081 + - NEXUS_SECURITY_RANDOMPASSWORD=false + - INSTALL4J_ADD_VM_PARAMS=-Xms2703m -Xmx2703m -XX:MaxDirectMemorySize=2703m -Djava.util.prefs.userRoot=/nexus-data/javaprefs + volumes: + - nexus_data:/nexus-data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8081/service/rest/v1/status"] + interval: 10s + timeout: 10s + retries: 3 + start_period: 60s From fdd8323598a50f5a9fe2be7d3d6d8afc41f1cc80 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:04:28 +0100 Subject: [PATCH 03/84] fix: nexus service --- public/svgs/nexus-arm.png | Bin 6698 -> 0 bytes templates/compose/nexus-arm.yaml | 3 +-- templates/compose/nexus.yaml | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 public/svgs/nexus-arm.png diff --git a/public/svgs/nexus-arm.png b/public/svgs/nexus-arm.png deleted file mode 100644 index 823ea4e867586dca08782ea294a33437302d8ab9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6698 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 VDob)N1ONO1ZeG*Vs=8_u_8*&hZtDO5 diff --git a/templates/compose/nexus-arm.yaml b/templates/compose/nexus-arm.yaml index d76578adc..b9a426309 100644 --- a/templates/compose/nexus-arm.yaml +++ b/templates/compose/nexus-arm.yaml @@ -1,13 +1,12 @@ # documentation: https://help.sonatype.com/en/sonatype-nexus-repository.html # slogan: Open source Universal Repository Manager (ARM version, community edition), default credentials: admin/admin123 # tags: repository, manager, open source, docker, docker, registry, container -# logo: svgs/nexus-arm.png +# logo: svgs/nexus.png # port: 8081 services: nexus: image: sonatypecommunity/nexus3 - restart: on-failure environment: - SERVICE_FQDN_NEXUS_8081 - NEXUS_SECURITY_RANDOMPASSWORD=false diff --git a/templates/compose/nexus.yaml b/templates/compose/nexus.yaml index de9f93075..a23601c1d 100644 --- a/templates/compose/nexus.yaml +++ b/templates/compose/nexus.yaml @@ -7,10 +7,10 @@ services: nexus: image: sonatype/nexus3 - restart: on-failure + platform: linux/amd64 environment: - SERVICE_FQDN_NEXUS_8081 - - NEXUS_SECURITY_RANDOMPASSWORD=false + - NEXUS_SECURITY_RANDOMPASSWORD=${NEXUS_SECURITY_RANDOMPASSWORD:-false} - INSTALL4J_ADD_VM_PARAMS=-Xms2703m -Xmx2703m -XX:MaxDirectMemorySize=2703m -Djava.util.prefs.userRoot=/nexus-data/javaprefs volumes: - nexus_data:/nexus-data From 249fc9e6ec2f77805ddedcdf1bae58293ea3f167 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:04:43 +0100 Subject: [PATCH 04/84] fix: fileflows volume names --- templates/compose/fileflows.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/compose/fileflows.yaml b/templates/compose/fileflows.yaml index 9beebb74e..7e2d9f7d9 100644 --- a/templates/compose/fileflows.yaml +++ b/templates/compose/fileflows.yaml @@ -16,10 +16,10 @@ services: #devices: # - "/dev/dri:/dev/dri" # Enable hardware transcoding volumes: - - fileflows-temp:/temp - - fileflows-configs:/app/Data - - fileflows-logs:/app/Logs - - fileflows-common:/app/common + - fileflows_temp:/temp + - fileflows_configs:/app/Data + - fileflows_logs:/app/Logs + - fileflows_common:/app/common healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5000/api/system/version"] From 70586ba21396ad45bb25d88425f74f4c9c8afaab Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:05:02 +0100 Subject: [PATCH 05/84] Update service-templates.json --- templates/service-templates.json | 36 +++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/templates/service-templates.json b/templates/service-templates.json index 514d995c5..7bedcbf5c 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -698,7 +698,7 @@ "fileflows": { "documentation": "https://fileflows.com/docs/?utm_source=coolify.io", "slogan": "FileFlows can drastically reduce your files, up to 90%, saving you space and money. No need to buy more hard drives, just shrink your files and start saving.", - "compose": "c2VydmljZXM6CiAgcmFkYXJyOgogICAgaW1hZ2U6IHJldmVuei9maWxlZmxvd3MKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9GSUxFRkxPV1NfNTAwMAogICAgICAtIF9BUFBfVVJMPSRTRVJWSUNFX0ZRRE5fRklMRV9GTE9XUwogICAgICAtIFBVSUQ9MTAwMAogICAgICAtIFBHSUQ9MTAwMAogICAgICAtICdUWj0ke1RaOi1BbWVyaWNhL1Rvcm9udG99JwogICAgdm9sdW1lczoKICAgICAgLSAnZmlsZWZsb3dzLXRlbXA6L3RlbXAnCiAgICAgIC0gJ2ZpbGVmbG93cy1jb25maWdzOi9hcHAvRGF0YScKICAgICAgLSAnZmlsZWZsb3dzLWxvZ3M6L2FwcC9Mb2dzJwogICAgICAtICdmaWxlZmxvd3MtY29tbW9uOi9hcHAvY29tbW9uJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjUwMDAvYXBpL3N5c3RlbS92ZXJzaW9uJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==", + "compose": "c2VydmljZXM6CiAgcmFkYXJyOgogICAgaW1hZ2U6IHJldmVuei9maWxlZmxvd3MKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9GSUxFRkxPV1NfNTAwMAogICAgICAtIF9BUFBfVVJMPSRTRVJWSUNFX0ZRRE5fRklMRV9GTE9XUwogICAgICAtIFBVSUQ9MTAwMAogICAgICAtIFBHSUQ9MTAwMAogICAgICAtICdUWj0ke1RaOi1BbWVyaWNhL1Rvcm9udG99JwogICAgdm9sdW1lczoKICAgICAgLSAnZmlsZWZsb3dzX3RlbXA6L3RlbXAnCiAgICAgIC0gJ2ZpbGVmbG93c19jb25maWdzOi9hcHAvRGF0YScKICAgICAgLSAnZmlsZWZsb3dzX2xvZ3M6L2FwcC9Mb2dzJwogICAgICAtICdmaWxlZmxvd3NfY29tbW9uOi9hcHAvY29tbW9uJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjUwMDAvYXBpL3N5c3RlbS92ZXJzaW9uJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==", "tags": [ "media", "transcode", @@ -1922,6 +1922,40 @@ "minversion": "0.0.0", "port": "80" }, + "nexus-arm": { + "documentation": "https://help.sonatype.com/en/sonatype-nexus-repository.html?utm_source=coolify.io", + "slogan": "Open source Universal Repository Manager (ARM version, community edition), default credentials: admin/admin123", + "compose": "c2VydmljZXM6CiAgbmV4dXM6CiAgICBpbWFnZTogc29uYXR5cGVjb21tdW5pdHkvbmV4dXMzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTkVYVVNfODA4MQogICAgICAtIE5FWFVTX1NFQ1VSSVRZX1JBTkRPTVBBU1NXT1JEPWZhbHNlCiAgICAgIC0gJ0lOU1RBTEw0Sl9BRERfVk1fUEFSQU1TPS1YbXMyNzAzbSAtWG14MjcwM20gLVhYOk1heERpcmVjdE1lbW9yeVNpemU9MjcwM20gLURqYXZhLnV0aWwucHJlZnMudXNlclJvb3Q9L25leHVzLWRhdGEvamF2YXByZWZzJwogICAgdm9sdW1lczoKICAgICAgLSAnbmV4dXNfZGF0YTovbmV4dXMtZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo4MDgxL3NlcnZpY2UvcmVzdC92MS9zdGF0dXMnCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMKICAgICAgc3RhcnRfcGVyaW9kOiA2MHMK", + "tags": [ + "repository", + "manager", + "open source", + "docker", + "docker", + "registry", + "container" + ], + "logo": "svgs/nexus.png", + "minversion": "0.0.0", + "port": "8081" + }, + "nexus": { + "documentation": "https://help.sonatype.com/en/sonatype-nexus-repository.html?utm_source=coolify.io", + "slogan": "Open source Universal Repository Manager (x86_64 version, official), default credentials: admin/admin123", + "compose": "c2VydmljZXM6CiAgbmV4dXM6CiAgICBpbWFnZTogc29uYXR5cGUvbmV4dXMzCiAgICBwbGF0Zm9ybTogbGludXgvYW1kNjQKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9ORVhVU184MDgxCiAgICAgIC0gJ05FWFVTX1NFQ1VSSVRZX1JBTkRPTVBBU1NXT1JEPSR7TkVYVVNfU0VDVVJJVFlfUkFORE9NUEFTU1dPUkQ6LWZhbHNlfScKICAgICAgLSAnSU5TVEFMTDRKX0FERF9WTV9QQVJBTVM9LVhtczI3MDNtIC1YbXgyNzAzbSAtWFg6TWF4RGlyZWN0TWVtb3J5U2l6ZT0yNzAzbSAtRGphdmEudXRpbC5wcmVmcy51c2VyUm9vdD0vbmV4dXMtZGF0YS9qYXZhcHJlZnMnCiAgICB2b2x1bWVzOgogICAgICAtICduZXh1c19kYXRhOi9uZXh1cy1kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwODEvc2VydmljZS9yZXN0L3YxL3N0YXR1cycKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwogICAgICBzdGFydF9wZXJpb2Q6IDYwcwo=", + "tags": [ + "repository", + "manager", + "open source", + "docker", + "docker", + "registry", + "container" + ], + "logo": "svgs/nexus.png", + "minversion": "0.0.0", + "port": "8081" + }, "nitropage-with-postgresql": { "documentation": "https://nitropage.com?utm_source=coolify.io", "slogan": "Nitropage is an extensible visual website builder, offering a growing collection of versatile building blocks, focal-point image cropping and sovereign font management.", From 1908d8a180dc2b595bc4494948ccb7ac6bddf504 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 11 Dec 2024 15:12:44 +0100 Subject: [PATCH 06/84] fix: --cone --- app/Models/Application.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/Models/Application.php b/app/Models/Application.php index 82ad0c2d1..7fc114094 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1331,7 +1331,9 @@ class Application extends BaseModel $currentPath = ''; foreach ($parts as $part) { $currentPath .= ($currentPath ? '/' : '').$part; - $paths->push($currentPath); + if (str($currentPath)->isNotEmpty()) { + $paths->push($currentPath); + } } return $paths; @@ -1341,7 +1343,7 @@ class Application extends BaseModel "mkdir -p /tmp/{$uuid}", "cd /tmp/{$uuid}", $cloneCommand, - 'git sparse-checkout init --cone', + 'git sparse-checkout init', "git sparse-checkout set {$fileList->implode(' ')}", 'git read-tree -mu HEAD', "cat .$workdir$composeFile", From a5b48caeb9f9f0ba164254f98ea743e011e7d7fb Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 11 Dec 2024 17:01:06 +0100 Subject: [PATCH 07/84] do prod migration with custom command --- app/Console/Commands/Migration.php | 24 +++++++++++++++++++ config/constants.php | 4 ++++ .../etc/s6-overlay/s6-rc.d/db-migration/up | 4 +--- 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 app/Console/Commands/Migration.php diff --git a/app/Console/Commands/Migration.php b/app/Console/Commands/Migration.php new file mode 100644 index 000000000..58b94fd55 --- /dev/null +++ b/app/Console/Commands/Migration.php @@ -0,0 +1,24 @@ +info('Migration is enabled on this server.'); + $this->call('migrate --force --isolated'); + exit(0); + } else { + $this->info('Migration is disabled on this server.'); + exit(0); + } + } +} diff --git a/config/constants.php b/config/constants.php index 4ea4411b6..3054df1f0 100644 --- a/config/constants.php +++ b/config/constants.php @@ -33,6 +33,10 @@ return [ 'app_key' => env('PUSHER_APP_KEY'), ], + 'migration' => [ + 'is_migration_enabled' => env('MIGRATION_ENABLED', true), + ], + 'horizon' => [ 'is_horizon_enabled' => env('HORIZON_ENABLED', true), 'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true), diff --git a/docker/production/etc/s6-overlay/s6-rc.d/db-migration/up b/docker/production/etc/s6-overlay/s6-rc.d/db-migration/up index f1a5006f1..c2548d136 100644 --- a/docker/production/etc/s6-overlay/s6-rc.d/db-migration/up +++ b/docker/production/etc/s6-overlay/s6-rc.d/db-migration/up @@ -6,8 +6,6 @@ cd /var/www/html foreground { php artisan - migrate - --force - --isolated + start:migration } From 97b6257872efc845116339ee3452708d2c8fd551 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:09:21 +0100 Subject: [PATCH 08/84] fix: provider error --- app/Providers/EventServiceProvider.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 2147495fa..aa3579f8d 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -9,6 +9,9 @@ use App\Listeners\ProxyStartedNotification; use Illuminate\Foundation\Events\MaintenanceModeDisabled; use Illuminate\Foundation\Events\MaintenanceModeEnabled; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; +use SocialiteProviders\Authentik\AuthentikExtendSocialite; +use SocialiteProviders\Azure\AzureExtendSocialite; +use SocialiteProviders\Manager\SocialiteWasCalled; class EventServiceProvider extends ServiceProvider { @@ -19,9 +22,9 @@ class EventServiceProvider extends ServiceProvider MaintenanceModeDisabled::class => [ MaintenanceModeDisabledNotification::class, ], - \SocialiteProviders\Manager\SocialiteWasCalled::class => [ - \SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle', - \SocialiteProviders\Authentik\AuthentikExtendSocialite::class.'@handle', + SocialiteWasCalled::class => [ + AzureExtendSocialite::class.'@handle', + AuthentikExtendSocialite::class.'@handle', ], ProxyStarted::class => [ ProxyStartedNotification::class, From edb14f886671d588a72f6764401c6a862b5f74b2 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:09:25 +0100 Subject: [PATCH 09/84] Update composer.lock --- composer.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.lock b/composer.lock index b7dfea43b..2cea9d3b6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d96fe0aedd865274164c34a1b04195fe", + "content-hash": "871067cb42e6347ca53ff36e81ac5079", "packages": [ { "name": "3sidedcube/laravel-redoc", @@ -15317,12 +15317,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" } From 528bfce83252211e4a060218a8267939677ce541 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 11 Dec 2024 17:09:53 +0100 Subject: [PATCH 10/84] fix: migration call --- app/Livewire/Project/Resource/Index.php | 175 +++++++++--------------- 1 file changed, 67 insertions(+), 108 deletions(-) diff --git a/app/Livewire/Project/Resource/Index.php b/app/Livewire/Project/Resource/Index.php index 283496887..0c2ea802a 100644 --- a/app/Livewire/Project/Resource/Index.php +++ b/app/Livewire/Project/Resource/Index.php @@ -46,125 +46,84 @@ class Index extends Component return redirect()->route('dashboard'); } $this->project = $project; - $this->environment = $environment; - $this->applications = $this->environment->applications->load(['tags']); + $this->environment = $environment->loadCount([ + 'applications', + 'redis', + 'postgresqls', + 'mysqls', + 'keydbs', + 'dragonflies', + 'clickhouses', + 'mariadbs', + 'mongodbs', + 'services', + ]); + + // Eager load all relationships for applications including nested ones + $this->applications = $this->environment->applications()->with([ + 'tags', + 'additional_servers.settings', + 'additional_networks', + 'destination.server.settings', + 'settings', + ])->get()->sortBy('name'); $this->applications = $this->applications->map(function ($application) { - if (data_get($application, 'environment.project.uuid')) { - $application->hrefLink = route('project.application.configuration', [ - 'project_uuid' => data_get($application, 'environment.project.uuid'), - 'environment_name' => data_get($application, 'environment.name'), - 'application_uuid' => data_get($application, 'uuid'), - ]); - } + $application->hrefLink = route('project.application.configuration', [ + 'project_uuid' => $this->project->uuid, + 'application_uuid' => $application->uuid, + 'environment_name' => $this->environment->name, + ]); return $application; }); - $this->postgresqls = $this->environment->postgresqls->load(['tags'])->sortBy('name'); - $this->postgresqls = $this->postgresqls->map(function ($postgresql) { - if (data_get($postgresql, 'environment.project.uuid')) { - $postgresql->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($postgresql, 'environment.project.uuid'), - 'environment_name' => data_get($postgresql, 'environment.name'), - 'database_uuid' => data_get($postgresql, 'uuid'), - ]); - } - return $postgresql; - }); - $this->redis = $this->environment->redis->load(['tags'])->sortBy('name'); - $this->redis = $this->redis->map(function ($redis) { - if (data_get($redis, 'environment.project.uuid')) { - $redis->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($redis, 'environment.project.uuid'), - 'environment_name' => data_get($redis, 'environment.name'), - 'database_uuid' => data_get($redis, 'uuid'), - ]); - } + // Load all database resources in a single query per type + $databaseTypes = [ + 'postgresqls' => 'postgresqls', + 'redis' => 'redis', + 'mongodbs' => 'mongodbs', + 'mysqls' => 'mysqls', + 'mariadbs' => 'mariadbs', + 'keydbs' => 'keydbs', + 'dragonflies' => 'dragonflies', + 'clickhouses' => 'clickhouses', + ]; - return $redis; - }); - $this->mongodbs = $this->environment->mongodbs->load(['tags'])->sortBy('name'); - $this->mongodbs = $this->mongodbs->map(function ($mongodb) { - if (data_get($mongodb, 'environment.project.uuid')) { - $mongodb->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($mongodb, 'environment.project.uuid'), - 'environment_name' => data_get($mongodb, 'environment.name'), - 'database_uuid' => data_get($mongodb, 'uuid'), - ]); - } + // Load all server-related data first to prevent duplicate queries + $serverData = $this->environment->applications() + ->with(['destination.server.settings']) + ->get() + ->pluck('destination.server') + ->filter() + ->unique('id'); - return $mongodb; - }); - $this->mysqls = $this->environment->mysqls->load(['tags'])->sortBy('name'); - $this->mysqls = $this->mysqls->map(function ($mysql) { - if (data_get($mysql, 'environment.project.uuid')) { - $mysql->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($mysql, 'environment.project.uuid'), - 'environment_name' => data_get($mysql, 'environment.name'), - 'database_uuid' => data_get($mysql, 'uuid'), + foreach ($databaseTypes as $property => $relation) { + $this->{$property} = $this->environment->{$relation}()->with([ + 'tags', + 'destination.server.settings', + ])->get()->sortBy('name'); + $this->{$property} = $this->{$property}->map(function ($db) { + $db->hrefLink = route('project.database.configuration', [ + 'project_uuid' => $this->project->uuid, + 'database_uuid' => $db->uuid, + 'environment_name' => $this->environment->name, ]); - } - return $mysql; - }); - $this->mariadbs = $this->environment->mariadbs->load(['tags'])->sortBy('name'); - $this->mariadbs = $this->mariadbs->map(function ($mariadb) { - if (data_get($mariadb, 'environment.project.uuid')) { - $mariadb->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($mariadb, 'environment.project.uuid'), - 'environment_name' => data_get($mariadb, 'environment.name'), - 'database_uuid' => data_get($mariadb, 'uuid'), - ]); - } + return $db; + }); + } - return $mariadb; - }); - $this->keydbs = $this->environment->keydbs->load(['tags'])->sortBy('name'); - $this->keydbs = $this->keydbs->map(function ($keydb) { - if (data_get($keydb, 'environment.project.uuid')) { - $keydb->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($keydb, 'environment.project.uuid'), - 'environment_name' => data_get($keydb, 'environment.name'), - 'database_uuid' => data_get($keydb, 'uuid'), - ]); - } - - return $keydb; - }); - $this->dragonflies = $this->environment->dragonflies->load(['tags'])->sortBy('name'); - $this->dragonflies = $this->dragonflies->map(function ($dragonfly) { - if (data_get($dragonfly, 'environment.project.uuid')) { - $dragonfly->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($dragonfly, 'environment.project.uuid'), - 'environment_name' => data_get($dragonfly, 'environment.name'), - 'database_uuid' => data_get($dragonfly, 'uuid'), - ]); - } - - return $dragonfly; - }); - $this->clickhouses = $this->environment->clickhouses->load(['tags'])->sortBy('name'); - $this->clickhouses = $this->clickhouses->map(function ($clickhouse) { - if (data_get($clickhouse, 'environment.project.uuid')) { - $clickhouse->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($clickhouse, 'environment.project.uuid'), - 'environment_name' => data_get($clickhouse, 'environment.name'), - 'database_uuid' => data_get($clickhouse, 'uuid'), - ]); - } - - return $clickhouse; - }); - $this->services = $this->environment->services->load(['tags'])->sortBy('name'); + // Load services with their tags and server + $this->services = $this->environment->services()->with([ + 'tags', + 'destination.server.settings', + ])->get()->sortBy('name'); $this->services = $this->services->map(function ($service) { - if (data_get($service, 'environment.project.uuid')) { - $service->hrefLink = route('project.service.configuration', [ - 'project_uuid' => data_get($service, 'environment.project.uuid'), - 'environment_name' => data_get($service, 'environment.name'), - 'service_uuid' => data_get($service, 'uuid'), - ]); - $service->status = $service->status(); - } + $service->hrefLink = route('project.service.configuration', [ + 'project_uuid' => $this->project->uuid, + 'service_uuid' => $service->uuid, + 'environment_name' => $this->environment->name, + ]); return $service; }); From 46e6c9db51005f99cdf7732fd13e9e1e62b12a68 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:13:13 +0100 Subject: [PATCH 11/84] chore: bump dep --- composer.lock | 340 +++++++++++++++++++++----------------------------- 1 file changed, 141 insertions(+), 199 deletions(-) diff --git a/composer.lock b/composer.lock index 2cea9d3b6..3fbe72afb 100644 --- a/composer.lock +++ b/composer.lock @@ -979,16 +979,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.334.2", + "version": "3.334.3", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "b19afc076bb1cc2617bdef76efd41587596109e7" + "reference": "6576a9fcfc6ae7c76aed3c6fa4c3864060f72d04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/b19afc076bb1cc2617bdef76efd41587596109e7", - "reference": "b19afc076bb1cc2617bdef76efd41587596109e7", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6576a9fcfc6ae7c76aed3c6fa4c3864060f72d04", + "reference": "6576a9fcfc6ae7c76aed3c6fa4c3864060f72d04", "shasum": "" }, "require": { @@ -1071,9 +1071,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.334.2" + "source": "https://github.com/aws/aws-sdk-php/tree/3.334.3" }, - "time": "2024-12-09T19:30:23+00:00" + "time": "2024-12-10T19:41:55+00:00" }, { "name": "bacon/bacon-qr-code", @@ -3313,16 +3313,16 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - }, "laravel": { - "providers": [ - "Laravel\\Socialite\\SocialiteServiceProvider" - ], "aliases": { "Socialite": "Laravel\\Socialite\\Facades\\Socialite" - } + }, + "providers": [ + "Laravel\\Socialite\\SocialiteServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "5.x-dev" } }, "autoload": { @@ -4037,16 +4037,16 @@ }, { "name": "league/oauth1-client", - "version": "v1.10.1", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth1-client.git", - "reference": "d6365b901b5c287dd41f143033315e2f777e1167" + "reference": "f9c94b088837eb1aae1ad7c4f23eb65cc6993055" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/d6365b901b5c287dd41f143033315e2f777e1167", - "reference": "d6365b901b5c287dd41f143033315e2f777e1167", + "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/f9c94b088837eb1aae1ad7c4f23eb65cc6993055", + "reference": "f9c94b088837eb1aae1ad7c4f23eb65cc6993055", "shasum": "" }, "require": { @@ -4107,9 +4107,9 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth1-client/issues", - "source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.1" + "source": "https://github.com/thephpleague/oauth1-client/tree/v1.11.0" }, - "time": "2022-04-15T14:02:14+00:00" + "time": "2024-12-10T19:59:05+00:00" }, { "name": "league/uri", @@ -5952,64 +5952,6 @@ }, "time": "2024-10-13T11:29:49+00:00" }, - { - "name": "phpstan/phpstan", - "version": "1.12.12", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", - "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], - "support": { - "docs": "https://phpstan.org/user-guide/getting-started", - "forum": "https://github.com/phpstan/phpstan/discussions", - "issues": "https://github.com/phpstan/phpstan/issues", - "security": "https://github.com/phpstan/phpstan/security/policy", - "source": "https://github.com/phpstan/phpstan-src" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - } - ], - "time": "2024-11-28T22:13:23+00:00" - }, { "name": "pimple/pimple", "version": "v3.5.0", @@ -7099,65 +7041,6 @@ ], "time": "2024-04-27T21:32:50+00:00" }, - { - "name": "rector/rector", - "version": "1.2.10", - "source": { - "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "40f9cf38c05296bd32f444121336a521a293fa61" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/40f9cf38c05296bd32f444121336a521a293fa61", - "reference": "40f9cf38c05296bd32f444121336a521a293fa61", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.12.5" - }, - "conflict": { - "rector/rector-doctrine": "*", - "rector/rector-downgrade-php": "*", - "rector/rector-phpunit": "*", - "rector/rector-symfony": "*" - }, - "suggest": { - "ext-dom": "To manipulate phpunit.xml via the custom-rule command" - }, - "bin": [ - "bin/rector" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Instant Upgrade and Automated Refactoring of any PHP code", - "keywords": [ - "automation", - "dev", - "migration", - "refactoring" - ], - "support": { - "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/1.2.10" - }, - "funding": [ - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2024-11-08T13:59:10+00:00" - }, { "name": "resend/resend-laravel", "version": "v0.15.0", @@ -8009,40 +7892,41 @@ }, { "name": "spatie/laravel-ray", - "version": "1.37.1", + "version": "1.39.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ray.git", - "reference": "c2bedfd1172648df2c80aaceb2541d70f1d9a5b9" + "reference": "31b601f98590606d20e76b5dd68578dc1642cd2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/c2bedfd1172648df2c80aaceb2541d70f1d9a5b9", - "reference": "c2bedfd1172648df2c80aaceb2541d70f1d9a5b9", + "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/31b601f98590606d20e76b5dd68578dc1642cd2c", + "reference": "31b601f98590606d20e76b5dd68578dc1642cd2c", "shasum": "" }, "require": { + "composer-runtime-api": "^2.2", "ext-json": "*", - "illuminate/contracts": "^7.20|^8.19|^9.0|^10.0|^11.0", - "illuminate/database": "^7.20|^8.19|^9.0|^10.0|^11.0", - "illuminate/queue": "^7.20|^8.19|^9.0|^10.0|^11.0", - "illuminate/support": "^7.20|^8.19|^9.0|^10.0|^11.0", - "php": "^7.4|^8.0", - "rector/rector": "^0.19.2|^1.0", + "illuminate/contracts": "^7.20 || ^8.19 || ^9.0 || ^10.0 || ^11.0", + "illuminate/database": "^7.20 || ^8.19 || ^9.0 || ^10.0 || ^11.0", + "illuminate/queue": "^7.20 || ^8.19 || ^9.0 || ^10.0 || ^11.0", + "illuminate/support": "^7.20 || ^8.19 || ^9.0 || ^10.0 || ^11.0", + "php": "^7.4 || ^8.0", "spatie/backtrace": "^1.0", - "spatie/ray": "^1.41.1", - "symfony/stopwatch": "4.2|^5.1|^6.0|^7.0", - "zbateson/mail-mime-parser": "^1.3.1|^2.0|^3.0" + "spatie/ray": "^1.41.3", + "symfony/stopwatch": "4.2 || ^5.1 || ^6.0 || ^7.0", + "zbateson/mail-mime-parser": "^1.3.1 || ^2.0 || ^3.0" }, "require-dev": { "guzzlehttp/guzzle": "^7.3", - "laravel/framework": "^7.20|^8.19|^9.0|^10.0|^11.0", - "orchestra/testbench-core": "^5.0|^6.0|^7.0|^8.0|^9.0", - "pestphp/pest": "^1.22|^2.0", - "phpstan/phpstan": "^1.10.57", - "phpunit/phpunit": "^9.3|^10.1", - "spatie/pest-plugin-snapshots": "^1.1|^2.0", - "symfony/var-dumper": "^4.2|^5.1|^6.0|^7.0.3" + "laravel/framework": "^7.20 || ^8.19 || ^9.0 || ^10.0 || ^11.0", + "orchestra/testbench-core": "^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0", + "pestphp/pest": "^1.22 || ^2.0", + "phpstan/phpstan": "^1.10.57 || ^2.0.2", + "phpunit/phpunit": "^9.3 || ^10.1", + "rector/rector": "dev-main", + "spatie/pest-plugin-snapshots": "^1.1 || ^2.0", + "symfony/var-dumper": "^4.2 || ^5.1 || ^6.0 || ^7.0.3" }, "type": "library", "extra": { @@ -8080,7 +7964,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-ray/issues", - "source": "https://github.com/spatie/laravel-ray/tree/1.37.1" + "source": "https://github.com/spatie/laravel-ray/tree/1.39.0" }, "funding": [ { @@ -8092,7 +7976,7 @@ "type": "other" } ], - "time": "2024-07-12T12:35:17+00:00" + "time": "2024-12-11T09:34:41+00:00" }, { "name": "spatie/laravel-schemaless-attributes", @@ -8582,16 +8466,16 @@ }, { "name": "symfony/console", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf" + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/23c8aae6d764e2bae02d2a99f7532a7f6ed619cf", - "reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf", + "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", "shasum": "" }, "require": { @@ -8655,7 +8539,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.0" + "source": "https://github.com/symfony/console/tree/v7.2.1" }, "funding": [ { @@ -8671,7 +8555,7 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:24:19+00:00" + "time": "2024-12-11T03:49:26+00:00" }, { "name": "symfony/css-selector", @@ -8807,16 +8691,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "672b3dd1ef8b87119b446d67c58c106c43f965fe" + "reference": "6150b89186573046167796fa5f3f76601d5145f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/672b3dd1ef8b87119b446d67c58c106c43f965fe", - "reference": "672b3dd1ef8b87119b446d67c58c106c43f965fe", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/6150b89186573046167796fa5f3f76601d5145f8", + "reference": "6150b89186573046167796fa5f3f76601d5145f8", "shasum": "" }, "require": { @@ -8862,7 +8746,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.2.0" + "source": "https://github.com/symfony/error-handler/tree/v7.2.1" }, "funding": [ { @@ -8878,7 +8762,7 @@ "type": "tidelift" } ], - "time": "2024-11-05T15:35:02+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/event-dispatcher", @@ -9180,16 +9064,16 @@ }, { "name": "symfony/http-kernel", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "6b4722a25e0aed1ccb4914b9bcbd493cc4676b4d" + "reference": "d8ae58eecae44c8e66833e76cc50a4ad3c002d97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6b4722a25e0aed1ccb4914b9bcbd493cc4676b4d", - "reference": "6b4722a25e0aed1ccb4914b9bcbd493cc4676b4d", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d8ae58eecae44c8e66833e76cc50a4ad3c002d97", + "reference": "d8ae58eecae44c8e66833e76cc50a4ad3c002d97", "shasum": "" }, "require": { @@ -9274,7 +9158,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.2.0" + "source": "https://github.com/symfony/http-kernel/tree/v7.2.1" }, "funding": [ { @@ -9290,7 +9174,7 @@ "type": "tidelift" } ], - "time": "2024-11-29T08:42:40+00:00" + "time": "2024-12-11T12:09:10+00:00" }, { "name": "symfony/mailer", @@ -9374,16 +9258,16 @@ }, { "name": "symfony/mime", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "cc84a4b81f62158c3846ac7ff10f696aae2b524d" + "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/cc84a4b81f62158c3846ac7ff10f696aae2b524d", - "reference": "cc84a4b81f62158c3846ac7ff10f696aae2b524d", + "url": "https://api.github.com/repos/symfony/mime/zipball/7f9617fcf15cb61be30f8b252695ed5e2bfac283", + "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283", "shasum": "" }, "require": { @@ -9438,7 +9322,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.2.0" + "source": "https://github.com/symfony/mime/tree/v7.2.1" }, "funding": [ { @@ -9454,7 +9338,7 @@ "type": "tidelift" } ], - "time": "2024-11-23T09:19:39+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/options-resolver", @@ -13272,17 +13156,75 @@ "time": "2024-11-21T15:12:59+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "11.0.7", + "name": "phpstan/phpstan", + "version": "1.12.12", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca" + "url": "https://github.com/phpstan/phpstan.git", + "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca", - "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", + "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2024-11-28T22:13:23+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118", + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118", "shasum": "" }, "require": { @@ -13301,7 +13243,7 @@ "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^11.4.1" + "phpunit/phpunit": "^11.5.0" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -13339,7 +13281,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.8" }, "funding": [ { @@ -13347,7 +13289,7 @@ "type": "github" } ], - "time": "2024-10-09T06:21:38+00:00" + "time": "2024-12-11T12:34:27+00:00" }, { "name": "phpunit/php-file-iterator", @@ -14665,16 +14607,16 @@ }, { "name": "spatie/error-solutions", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/spatie/error-solutions.git", - "reference": "ae7393122eda72eed7cc4f176d1e96ea444f2d67" + "reference": "d239a65235a1eb128dfa0a4e4c4ef032ea11b541" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/error-solutions/zipball/ae7393122eda72eed7cc4f176d1e96ea444f2d67", - "reference": "ae7393122eda72eed7cc4f176d1e96ea444f2d67", + "url": "https://api.github.com/repos/spatie/error-solutions/zipball/d239a65235a1eb128dfa0a4e4c4ef032ea11b541", + "reference": "d239a65235a1eb128dfa0a4e4c4ef032ea11b541", "shasum": "" }, "require": { @@ -14727,7 +14669,7 @@ ], "support": { "issues": "https://github.com/spatie/error-solutions/issues", - "source": "https://github.com/spatie/error-solutions/tree/1.1.1" + "source": "https://github.com/spatie/error-solutions/tree/1.1.2" }, "funding": [ { @@ -14735,7 +14677,7 @@ "type": "github" } ], - "time": "2024-07-25T11:06:04+00:00" + "time": "2024-12-11T09:51:56+00:00" }, { "name": "spatie/flare-client-php", @@ -15317,12 +15259,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } From 7be3c6f2b752b15e34b7e722a8e56c6f2f849914 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 11 Dec 2024 17:18:32 +0100 Subject: [PATCH 12/84] refactor: improve migration command and enhance application model with global scope and status checks --- app/Console/Commands/Migration.php | 2 +- app/Models/Application.php | 33 ++++++++++++++++++------------ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/app/Console/Commands/Migration.php b/app/Console/Commands/Migration.php index 58b94fd55..44c17203b 100644 --- a/app/Console/Commands/Migration.php +++ b/app/Console/Commands/Migration.php @@ -14,7 +14,7 @@ class Migration extends Command { if (config('constants.migration.is_migration_enabled')) { $this->info('Migration is enabled on this server.'); - $this->call('migrate --force --isolated'); + $this->call('migrate', ['--force' => true, '--isolated' => true]); exit(0); } else { $this->info('Migration is disabled on this server.'); diff --git a/app/Models/Application.php b/app/Models/Application.php index 7fc114094..bfb2a1041 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -115,6 +115,12 @@ class Application extends BaseModel protected static function booted() { + static::addGlobalScope('withRelations', function ($builder) { + $builder->withCount([ + 'additional_servers', + 'additional_networks', + ]); + }); static::saving(function ($application) { $payload = []; if ($application->isDirty('fqdn')) { @@ -551,20 +557,21 @@ class Application extends BaseModel { return Attribute::make( get: function () { - if ($this->additional_servers->count() === 0) { - return $this->destination->server->isFunctional(); - } else { - $additional_servers_status = $this->additional_servers->pluck('pivot.status'); - $main_server_status = $this->destination->server->isFunctional(); - foreach ($additional_servers_status as $status) { - $server_status = str($status)->before(':')->value(); - if ($server_status !== 'running') { - return false; - } - } - - return $main_server_status; + if (! $this->relationLoaded('additional_servers') || $this->additional_servers->count() === 0) { + return $this->destination?->server?->isFunctional() ?? false; } + + $additional_servers_status = $this->additional_servers->pluck('pivot.status'); + $main_server_status = $this->destination?->server?->isFunctional() ?? false; + + foreach ($additional_servers_status as $status) { + $server_status = str($status)->before(':')->value(); + if ($server_status !== 'running') { + return false; + } + } + + return $main_server_status; } ); } From aeb97401ba43905a268728aae7073fe2f0effbc6 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:19:49 +0100 Subject: [PATCH 13/84] fix: database migration --- ...18_add_authentik_base_url_to_oauth_settings_table.php} | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) rename database/migrations/{2024_09_29_163625_add_base_url_to_oauth_settings_table.php => 2024_12_11_161418_add_authentik_base_url_to_oauth_settings_table.php} (64%) diff --git a/database/migrations/2024_09_29_163625_add_base_url_to_oauth_settings_table.php b/database/migrations/2024_12_11_161418_add_authentik_base_url_to_oauth_settings_table.php similarity index 64% rename from database/migrations/2024_09_29_163625_add_base_url_to_oauth_settings_table.php rename to database/migrations/2024_12_11_161418_add_authentik_base_url_to_oauth_settings_table.php index 8df9b48a2..44ecb0cde 100644 --- a/database/migrations/2024_09_29_163625_add_base_url_to_oauth_settings_table.php +++ b/database/migrations/2024_12_11_161418_add_authentik_base_url_to_oauth_settings_table.php @@ -12,9 +12,7 @@ return new class extends Migration public function up(): void { Schema::table('oauth_settings', function (Blueprint $table) { - Schema::table('oauth_settings', function (Blueprint $table) { - $table->string('base_url')->nullable(); - }); + $table->string('base_url')->nullable(); }); } @@ -24,9 +22,7 @@ return new class extends Migration public function down(): void { Schema::table('oauth_settings', function (Blueprint $table) { - Schema::table('oauth_settings', function (Blueprint $table) { - $table->dropColumn('base_url'); - }); + $table->dropColumn('base_url'); }); } }; From 737f70387ca0d7bf08637ea66095c575a3c71384 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:27:36 +0100 Subject: [PATCH 14/84] fix: seeder - do not hardcode IDs as this is bad practice. - Use update or create to allow the seeder to be run multiple times. --- database/seeders/OauthSettingSeeder.php | 38 +++++++++---------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/database/seeders/OauthSettingSeeder.php b/database/seeders/OauthSettingSeeder.php index d0365c542..2b733120c 100644 --- a/database/seeders/OauthSettingSeeder.php +++ b/database/seeders/OauthSettingSeeder.php @@ -12,29 +12,19 @@ class OauthSettingSeeder extends Seeder */ public function run(): void { - OauthSetting::firstOrCreate([ - 'id' => 0, - 'provider' => 'azure', - ]); - OauthSetting::firstOrCreate([ - 'id' => 1, - 'provider' => 'bitbucket', - ]); - OauthSetting::firstOrCreate([ - 'id' => 2, - 'provider' => 'github', - ]); - OauthSetting::firstOrCreate([ - 'id' => 3, - 'provider' => 'gitlab', - ]); - OauthSetting::firstOrCreate([ - 'id' => 4, - 'provider' => 'google', - ]); - OauthSetting::firstOrCreate([ - 'id' => 5, - 'provider' => 'authentik', - ]); + $providers = [ + 'azure', + 'bitbucket', + 'github', + 'gitlab', + 'google', + 'authentik', + ]; + + foreach ($providers as $provider) { + OauthSetting::updateOrCreate( + ['provider' => $provider] + ); + } } } From dd050192f5f125adf6c365b25ce2696cb8cbcacf Mon Sep 17 00:00:00 2001 From: Zairig Imad Date: Wed, 11 Dec 2024 18:06:16 +0100 Subject: [PATCH 15/84] Add new service dolibarr --- public/svgs/dolibarr.png | Bin 0 -> 195577 bytes templates/compose/dolibarr.yaml | 42 ++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 public/svgs/dolibarr.png create mode 100644 templates/compose/dolibarr.yaml diff --git a/public/svgs/dolibarr.png b/public/svgs/dolibarr.png new file mode 100644 index 0000000000000000000000000000000000000000..f09119afc966a39f23c77dae7271c7c0a5bcbeac GIT binary patch literal 195577 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%0AR4|Yc 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
  • slack
  • discord
  • telegram
  • +
  • pushover
  • @@ -72,6 +73,6 @@ class NotifyDemo extends Command
    In which manner you wish a coolified notification?
    - HTML, ['email', 'slack', 'discord', 'telegram']); + HTML, ['email', 'slack', 'discord', 'telegram', 'pushover']); } } diff --git a/app/Jobs/SendMessageToPushoverJob.php b/app/Jobs/SendMessageToPushoverJob.php new file mode 100644 index 000000000..834a32b07 --- /dev/null +++ b/app/Jobs/SendMessageToPushoverJob.php @@ -0,0 +1,50 @@ +onQueue('high'); + } + + /** + * Execute the job. + */ + public function handle(): void + { + $response = Http::post('https://api.pushover.net/1/messages.json', $this->message->toPayload($this->token, $this->user)); + if ($response->failed()) { + throw new \RuntimeException('Pushover notification failed with ' . $response->status() . ' status code.' . $response->body()); + } + } +} diff --git a/app/Livewire/Notifications/Pushover.php b/app/Livewire/Notifications/Pushover.php new file mode 100644 index 000000000..7a3b294a2 --- /dev/null +++ b/app/Livewire/Notifications/Pushover.php @@ -0,0 +1,176 @@ +team = auth()->user()->currentTeam(); + $this->settings = $this->team->pushoverNotificationSettings; + $this->syncData(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function syncData(bool $toModel = false) + { + if ($toModel) { + $this->validate(); + $this->settings->pushover_enabled = $this->pushoverEnabled; + $this->settings->pushover_user = $this->pushoverUser; + $this->settings->pushover_token = $this->pushoverToken; + + $this->settings->deployment_success_pushover_notifications = $this->deploymentSuccessPushoverNotifications; + $this->settings->deployment_failure_pushover_notifications = $this->deploymentFailurePushoverNotifications; + $this->settings->status_change_pushover_notifications = $this->statusChangePushoverNotifications; + $this->settings->backup_success_pushover_notifications = $this->backupSuccessPushoverNotifications; + $this->settings->backup_failure_pushover_notifications = $this->backupFailurePushoverNotifications; + $this->settings->scheduled_task_success_pushover_notifications = $this->scheduledTaskSuccessPushoverNotifications; + $this->settings->scheduled_task_failure_pushover_notifications = $this->scheduledTaskFailurePushoverNotifications; + $this->settings->docker_cleanup_success_pushover_notifications = $this->dockerCleanupSuccessPushoverNotifications; + $this->settings->docker_cleanup_failure_pushover_notifications = $this->dockerCleanupFailurePushoverNotifications; + $this->settings->server_disk_usage_pushover_notifications = $this->serverDiskUsagePushoverNotifications; + $this->settings->server_reachable_pushover_notifications = $this->serverReachablePushoverNotifications; + $this->settings->server_unreachable_pushover_notifications = $this->serverUnreachablePushoverNotifications; + + $this->settings->save(); + refreshSession(); + } else { + $this->pushoverEnabled = $this->settings->pushover_enabled; + $this->pushoverUser = $this->settings->pushover_user; + $this->pushoverToken = $this->settings->pushover_token; + + $this->deploymentSuccessPushoverNotifications = $this->settings->deployment_success_pushover_notifications; + $this->deploymentFailurePushoverNotifications = $this->settings->deployment_failure_pushover_notifications; + $this->statusChangePushoverNotifications = $this->settings->status_change_pushover_notifications; + $this->backupSuccessPushoverNotifications = $this->settings->backup_success_pushover_notifications; + $this->backupFailurePushoverNotifications = $this->settings->backup_failure_pushover_notifications; + $this->scheduledTaskSuccessPushoverNotifications = $this->settings->scheduled_task_success_pushover_notifications; + $this->scheduledTaskFailurePushoverNotifications = $this->settings->scheduled_task_failure_pushover_notifications; + $this->dockerCleanupSuccessPushoverNotifications = $this->settings->docker_cleanup_success_pushover_notifications; + $this->dockerCleanupFailurePushoverNotifications = $this->settings->docker_cleanup_failure_pushover_notifications; + $this->serverDiskUsagePushoverNotifications = $this->settings->server_disk_usage_pushover_notifications; + $this->serverReachablePushoverNotifications = $this->settings->server_reachable_pushover_notifications; + $this->serverUnreachablePushoverNotifications = $this->settings->server_unreachable_pushover_notifications; + } + } + + public function instantSavePushoverEnabled() + { + try { + $this->validate([ + 'pushoverUser' => 'required', + 'pushoverToken' => 'required', + ], [ + 'pushoverUser.required' => 'Pushover User is required.', + 'pushoverToken.required' => 'Pushover Token is required.', + ]); + $this->saveModel(); + } catch (\Throwable $e) { + $this->pushoverEnabled = false; + + return handleError($e, $this); + } + } + + public function instantSave() + { + try { + $this->syncData(true); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function submit() + { + try { + $this->resetErrorBag(); + $this->syncData(true); + $this->saveModel(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function saveModel() + { + $this->syncData(true); + refreshSession(); + $this->dispatch('success', 'Settings saved.'); + } + + public function sendTestNotification() + { + try { + $this->team->notify(new Test(channel: 'pushover')); + $this->dispatch('success', 'Test notification sent.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function render() + { + return view('livewire.notifications.pushover'); + } +} diff --git a/app/Models/PushoverNotificationSettings.php b/app/Models/PushoverNotificationSettings.php new file mode 100644 index 000000000..7576a6040 --- /dev/null +++ b/app/Models/PushoverNotificationSettings.php @@ -0,0 +1,61 @@ + 'boolean', + 'pushover_user' => 'encrypted', + 'pushover_token' => 'encrypted', + + 'deployment_success_pushover_notifications' => 'boolean', + 'deployment_failure_pushover_notifications' => 'boolean', + 'status_change_pushover_notifications' => 'boolean', + 'backup_success_pushover_notifications' => 'boolean', + 'backup_failure_pushover_notifications' => 'boolean', + 'scheduled_task_success_pushover_notifications' => 'boolean', + 'scheduled_task_failure_pushover_notifications' => 'boolean', + 'docker_cleanup_pushover_notifications' => 'boolean', + 'server_disk_usage_pushover_notifications' => 'boolean', + 'server_reachable_pushover_notifications' => 'boolean', + 'server_unreachable_pushover_notifications' => 'boolean', + ]; + + public function team() + { + return $this->belongsTo(Team::class); + } + + public function isEnabled() + { + return $this->pushover_enabled; + } +} diff --git a/app/Models/Team.php b/app/Models/Team.php index 07424a55f..fc10892a4 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -5,6 +5,7 @@ namespace App\Models; use App\Notifications\Channels\SendsDiscord; use App\Notifications\Channels\SendsEmail; use App\Notifications\Channels\SendsSlack; +use App\Notifications\Channels\SendsPushover; use App\Traits\HasNotificationSettings; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; @@ -32,7 +33,7 @@ use OpenApi\Attributes as OA; ] )] -class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack +class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack, SendsPushover { use HasNotificationSettings, Notifiable; @@ -49,6 +50,7 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack $team->discordNotificationSettings()->create(); $team->slackNotificationSettings()->create(); $team->telegramNotificationSettings()->create(); + $team->pushoverNotificationSettings()->create(); }); static::saving(function ($team) { @@ -154,6 +156,14 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack { return data_get($this, 'slack_webhook_url', null); } + + public function routeNotificationForPushover() + { + return [ + 'user' => data_get($this, 'pushover_user', null), + 'token' => data_get($this, 'pushover_token', null), + ]; + } public function getRecipients($notification) { @@ -174,7 +184,8 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack return $this->getNotificationSettings('email')?->isEnabled() || $this->getNotificationSettings('discord')?->isEnabled() || $this->getNotificationSettings('slack')?->isEnabled() || - $this->getNotificationSettings('telegram')?->isEnabled(); + $this->getNotificationSettings('telegram')?->isEnabled() || + $this->getNotificationSettings('pushover')?->isEnabled(); } public function subscriptionEnded() @@ -276,4 +287,9 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack { return $this->hasOne(SlackNotificationSettings::class); } + + public function pushoverNotificationSettings() + { + return $this->hasOne(PushoverNotificationSettings::class); + } } diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php index 06a2b48e3..80c1c421c 100644 --- a/app/Notifications/Application/DeploymentFailed.php +++ b/app/Notifications/Application/DeploymentFailed.php @@ -6,6 +6,7 @@ use App\Models\Application; use App\Models\ApplicationPreview; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -130,6 +131,31 @@ class DeploymentFailed extends CustomEmailNotification ]; } + public function toPushover(): PushoverMessage + { + if ($this->preview) { + $title = "Pull request #{$this->preview->pull_request_id} deployment failed"; + $message = "Pull request deployment failed for {$this->application_name}"; + } else { + $title = 'Deployment failed'; + $message = "Deployment failed for {$this->application_name}"; + } + + $buttons[] = [ + 'text' => 'Deployment logs', + 'url' => $this->deployment_url, + ]; + + return new PushoverMessage( + title: $title, + level: 'error', + message: $message, + buttons: [ + ...$buttons, + ], + ); + } + public function toSlack(): SlackMessage { if ($this->preview) { diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php index 548f5c6e5..b1a3d5225 100644 --- a/app/Notifications/Application/DeploymentSuccess.php +++ b/app/Notifications/Application/DeploymentSuccess.php @@ -6,6 +6,7 @@ use App\Models\Application; use App\Models\ApplicationPreview; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -139,6 +140,42 @@ class DeploymentSuccess extends CustomEmailNotification ]; } + public function toPushover(): PushoverMessage + { + if ($this->preview) { + $title = "Pull request #{$this->preview->pull_request_id} successfully deployed"; + $message = 'New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . ''; + if ($this->preview->fqdn) { + $buttons[] = [ + 'text' => 'Open Application', + 'url' => $this->preview->fqdn, + ]; + } + } else { + $title = 'New version successfully deployed'; + $message = 'New version successfully deployed of ' . $this->application_name . ''; + if ($this->fqdn) { + $buttons[] = [ + 'text' => 'Open Application', + 'url' => $this->fqdn, + ]; + } + } + $buttons[] = [ + 'text' => 'Deployment logs', + 'url' => $this->deployment_url, + ]; + + return new PushoverMessage( + title: $title, + level: 'success', + message: $message, + buttons: [ + ...$buttons, + ], + ); + } + public function toSlack(): SlackMessage { if ($this->preview) { diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php index 32a6a659a..c9c7344c4 100644 --- a/app/Notifications/Application/StatusChanged.php +++ b/app/Notifications/Application/StatusChanged.php @@ -5,6 +5,7 @@ namespace App\Notifications\Application; use App\Models\Application; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -77,6 +78,23 @@ class StatusChanged extends CustomEmailNotification ]; } + public function toPushover(): PushoverMessage + { + $message = $this->resource_name . ' has been stopped.'; + + return new PushoverMessage( + title: 'Application stopped', + level: 'error', + message: $message, + buttons: [ + [ + 'text' => 'Open Application in Coolify', + 'url' => $this->resource_url, + ], + ], + ); + } + public function toSlack(): SlackMessage { $title = 'Application stopped'; diff --git a/app/Notifications/Channels/PushoverChannel.php b/app/Notifications/Channels/PushoverChannel.php new file mode 100644 index 000000000..56d3cb03d --- /dev/null +++ b/app/Notifications/Channels/PushoverChannel.php @@ -0,0 +1,21 @@ +toPushover(); + $pushoverSettings = $notifiable->pushoverNotificationSettings; + + if (! $pushoverSettings || ! $pushoverSettings->isEnabled() || ! $pushoverSettings->pushover_user || ! $pushoverSettings->pushover_token) { + return; + } + + SendMessageToPushoverJob::dispatch($message, $pushoverSettings->pushover_token, $pushoverSettings->pushover_user); + } +} diff --git a/app/Notifications/Channels/SendsPushover.php b/app/Notifications/Channels/SendsPushover.php new file mode 100644 index 000000000..7922eefb4 --- /dev/null +++ b/app/Notifications/Channels/SendsPushover.php @@ -0,0 +1,8 @@ +url) { + $buttons[] = [ + 'text' => 'Check Proxy in Coolify', + 'url' => $this->url, + ]; + } + + return new PushoverMessage( + title: 'Resource restarted', + level: 'warning', + message: "A resource ({$this->name}) has been restarted automatically on {$this->server->name}", + buttons: $buttons, + ); + } + public function toSlack(): SlackMessage { $title = 'Resource restarted'; diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php index bc6e52b6d..49aea196d 100644 --- a/app/Notifications/Container/ContainerStopped.php +++ b/app/Notifications/Container/ContainerStopped.php @@ -5,6 +5,7 @@ namespace App\Notifications\Container; use App\Models\Server; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -68,6 +69,25 @@ class ContainerStopped extends CustomEmailNotification return $payload; } + public function toPushover(): PushoverMessage + { + $buttons = []; + if ($this->url) { + $buttons[] = [ + 'text' => 'Open Application in Coolify', + 'url' => $this->url, + ]; + } + + return new PushoverMessage( + title: 'Resource stopped', + level: 'error', + message: "A resource ({$this->name}) has been stopped unexpectedly on {$this->server->name}", + buttons: $buttons, + ); + } + + public function toSlack(): SlackMessage { $title = 'Resource stopped'; diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php index 2208f7b1d..6dcb70583 100644 --- a/app/Notifications/Database/BackupFailed.php +++ b/app/Notifications/Database/BackupFailed.php @@ -5,6 +5,7 @@ namespace App\Notifications\Database; use App\Models\ScheduledDatabaseBackup; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -64,6 +65,15 @@ class BackupFailed extends CustomEmailNotification ]; } + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Database backup failed', + level: 'error', + message: "Database backup for {$this->name} (db:{$this->database_name}) was FAILED

    Frequency: {$this->frequency} .
    Reason: {$this->output}", + ); + } + public function toSlack(): SlackMessage { $title = 'Database backup failed'; diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php index 10b4ff3df..4c3e8e060 100644 --- a/app/Notifications/Database/BackupSuccess.php +++ b/app/Notifications/Database/BackupSuccess.php @@ -5,6 +5,7 @@ namespace App\Notifications\Database; use App\Models\ScheduledDatabaseBackup; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -62,6 +63,17 @@ class BackupSuccess extends CustomEmailNotification ]; } + + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Database backup successful', + level: 'success', + message: "Database backup for {$this->name} (db:{$this->database_name}) was successful.

    Frequency: {$this->frequency}.", + ); + } + + public function toSlack(): SlackMessage { $title = 'Database backup successful'; diff --git a/app/Notifications/Dto/PushoverMessage.php b/app/Notifications/Dto/PushoverMessage.php new file mode 100644 index 000000000..6022c2885 --- /dev/null +++ b/app/Notifications/Dto/PushoverMessage.php @@ -0,0 +1,50 @@ +level) { + 'info' => 'ℹ️', + 'error' => '🚨', + 'success' => '🟢', + 'warning' => '⚠️', + }; + } + + public function toPayload(string $token, string $user): array + { + $levelIcon = $this->getLevelIcon(); + $payload = [ + 'token' => $token, + 'user' => $user, + 'title' => "{$levelIcon} {$this->title}", + 'message' => $this->message, + 'html' => 1, + ]; + + foreach ($this->buttons as $button) { + $buttonUrl = data_get($button, 'url'); + $text = data_get($button, 'text', 'Click here'); + if ($buttonUrl && str_contains($buttonUrl, 'http://localhost')) { + $buttonUrl = str_replace('http://localhost', config('app.url'), $buttonUrl); + } + $payload['message'] .= " 
    " . $text . ''; + } + + Log::info('Pushover message', $payload); + + return $payload; + } +} diff --git a/app/Notifications/Internal/GeneralNotification.php b/app/Notifications/Internal/GeneralNotification.php index 7049e3055..1d2367210 100644 --- a/app/Notifications/Internal/GeneralNotification.php +++ b/app/Notifications/Internal/GeneralNotification.php @@ -3,6 +3,7 @@ namespace App\Notifications\Internal; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -40,6 +41,15 @@ class GeneralNotification extends Notification implements ShouldQueue ]; } + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'General Notification', + level: 'info', + message: $this->message, + ); + } + public function toSlack(): SlackMessage { return new SlackMessage( diff --git a/app/Notifications/ScheduledTask/TaskFailed.php b/app/Notifications/ScheduledTask/TaskFailed.php index 56c410ecb..c4d92f213 100644 --- a/app/Notifications/ScheduledTask/TaskFailed.php +++ b/app/Notifications/ScheduledTask/TaskFailed.php @@ -5,6 +5,7 @@ namespace App\Notifications\ScheduledTask; use App\Models\ScheduledTask; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -70,6 +71,30 @@ class TaskFailed extends CustomEmailNotification ]; } + public function toPushover(): PushoverMessage + { + $message = "Scheduled task ({$this->task->name}) failed
    "; + + if ($this->output) { + $message .= "
    Error Output:{$this->output}"; + } + + $buttons = []; + if ($this->url) { + $buttons[] = [ + 'text' => 'Open task in Coolify', + 'url' => (string) $this->url, + ]; + } + + return new PushoverMessage( + title: 'Scheduled task failed', + level: 'error', + message: $message, + buttons: $buttons, + ); + } + public function toSlack(): SlackMessage { $title = 'Scheduled task failed'; diff --git a/app/Notifications/ScheduledTask/TaskSuccess.php b/app/Notifications/ScheduledTask/TaskSuccess.php index fc79aea37..5d4154e7a 100644 --- a/app/Notifications/ScheduledTask/TaskSuccess.php +++ b/app/Notifications/ScheduledTask/TaskSuccess.php @@ -5,6 +5,7 @@ namespace App\Notifications\ScheduledTask; use App\Models\ScheduledTask; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -70,6 +71,25 @@ class TaskSuccess extends CustomEmailNotification ]; } + public function toPushover(): PushoverMessage + { + $message = "Coolify: Scheduled task ({$this->task->name}) succeeded."; + $buttons = []; + if ($this->url) { + $buttons[] = [ + 'text' => 'Open task in Coolify', + 'url' => (string) $this->url, + ]; + } + + return new PushoverMessage( + title: 'Scheduled task succeeded', + level: 'success', + message: $message, + buttons: $buttons, + ); + } + public function toSlack(): SlackMessage { $title = 'Scheduled task succeeded'; diff --git a/app/Notifications/Server/DockerCleanupFailed.php b/app/Notifications/Server/DockerCleanupFailed.php index 53714925c..0291eed19 100644 --- a/app/Notifications/Server/DockerCleanupFailed.php +++ b/app/Notifications/Server/DockerCleanupFailed.php @@ -5,6 +5,7 @@ namespace App\Notifications\Server; use App\Models\Server; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -48,6 +49,15 @@ class DockerCleanupFailed extends CustomEmailNotification ]; } + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Docker cleanup job failed', + level: 'error', + message: "[ACTION REQUIRED] Docker cleanup job failed on {$this->server->name}!\n\n{$this->message}", + ); + } + public function toSlack(): SlackMessage { return new SlackMessage( diff --git a/app/Notifications/Server/DockerCleanupSuccess.php b/app/Notifications/Server/DockerCleanupSuccess.php index 85a819da2..1a652d189 100644 --- a/app/Notifications/Server/DockerCleanupSuccess.php +++ b/app/Notifications/Server/DockerCleanupSuccess.php @@ -5,6 +5,7 @@ namespace App\Notifications\Server; use App\Models\Server; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -48,6 +49,15 @@ class DockerCleanupSuccess extends CustomEmailNotification ]; } + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Docker cleanup job succeeded', + level: 'success', + message: "Docker cleanup job succeeded on {$this->server->name}!\n\n{$this->message}", + ); + } + public function toSlack(): SlackMessage { return new SlackMessage( diff --git a/app/Notifications/Server/ForceDisabled.php b/app/Notifications/Server/ForceDisabled.php index e122849da..7a1f7bcbf 100644 --- a/app/Notifications/Server/ForceDisabled.php +++ b/app/Notifications/Server/ForceDisabled.php @@ -5,6 +5,7 @@ namespace App\Notifications\Server; use App\Models\Server; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -51,6 +52,15 @@ class ForceDisabled extends CustomEmailNotification ]; } + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Server disabled', + level: 'error', + message: "Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.
    Please update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).", + ); + } + public function toSlack(): SlackMessage { $title = 'Server disabled'; diff --git a/app/Notifications/Server/ForceEnabled.php b/app/Notifications/Server/ForceEnabled.php index a0e0a8b59..36dad3c60 100644 --- a/app/Notifications/Server/ForceEnabled.php +++ b/app/Notifications/Server/ForceEnabled.php @@ -5,6 +5,7 @@ namespace App\Notifications\Server; use App\Models\Server; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -47,6 +48,15 @@ class ForceEnabled extends CustomEmailNotification ]; } + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Server enabled', + level: 'success', + message: "Server ({$this->server->name}) enabled again!", + ); + } + public function toSlack(): SlackMessage { return new SlackMessage( diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php index 9f4826689..aea9abd03 100644 --- a/app/Notifications/Server/HighDiskUsage.php +++ b/app/Notifications/Server/HighDiskUsage.php @@ -5,6 +5,7 @@ namespace App\Notifications\Server; use App\Models\Server; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -57,6 +58,19 @@ class HighDiskUsage extends CustomEmailNotification ]; } + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'High disk usage detected', + level: 'warning', + message: "Server '{$this->server->name}' high disk usage detected!

    Disk usage: {$this->disk_usage}%.
    Threshold: {$this->server_disk_usage_notification_threshold}%.
    Please cleanup your disk to prevent data-loss.", + buttons: [ + 'Change settings' => base_url().'/server/'.$this->server->uuid."#advanced", + 'Tips for cleanup' => "https://coolify.io/docs/knowledge-base/server/automated-cleanup", + ], + ); + } + public function toSlack(): SlackMessage { $description = "Server '{$this->server->name}' high disk usage detected!\n"; diff --git a/app/Notifications/Server/Reachable.php b/app/Notifications/Server/Reachable.php index 4917e04f8..e03aef6b7 100644 --- a/app/Notifications/Server/Reachable.php +++ b/app/Notifications/Server/Reachable.php @@ -5,6 +5,7 @@ namespace App\Notifications\Server; use App\Models\Server; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -49,6 +50,15 @@ class Reachable extends CustomEmailNotification ); } + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Server revived', + message: "Server '{$this->server->name}' revived. All automations & integrations are turned on again!", + level: 'success', + ); + } + public function toTelegram(): array { return [ diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php index 43f176d49..fe90cc610 100644 --- a/app/Notifications/Server/Unreachable.php +++ b/app/Notifications/Server/Unreachable.php @@ -5,6 +5,7 @@ namespace App\Notifications\Server; use App\Models\Server; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; @@ -60,6 +61,15 @@ class Unreachable extends CustomEmailNotification ]; } + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Server unreachable', + level: 'error', + message: "Your server '{$this->server->name}' is unreachable.
    All automations & integrations are turned off!

    IMPORTANT: We automatically try to revive your server and turn on all automations & integrations.", + ); + } + public function toSlack(): SlackMessage { $description = "Your server '{$this->server->name}' is unreachable.\n"; diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index da9098500..65971a0ee 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -6,7 +6,9 @@ use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\SlackChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\PushoverChannel; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -33,6 +35,7 @@ class Test extends Notification implements ShouldQueue 'discord' => [DiscordChannel::class], 'telegram' => [TelegramChannel::class], 'slack' => [SlackChannel::class], + 'pushover' => [PushoverChannel::class], default => [], }; } else { @@ -85,6 +88,20 @@ class Test extends Notification implements ShouldQueue ]; } + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Test Pushover Notification', + message: 'This is a test Pushover notification from Coolify.', + buttons: [ + [ + 'text' => 'Go to your dashboard', + 'url' => base_url(), + ], + ], + ); + } + public function toSlack(): SlackMessage { return new SlackMessage( diff --git a/app/Traits/HasNotificationSettings.php b/app/Traits/HasNotificationSettings.php index 82cbda6ad..ef858d0b6 100644 --- a/app/Traits/HasNotificationSettings.php +++ b/app/Traits/HasNotificationSettings.php @@ -6,6 +6,7 @@ use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\SlackChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\PushoverChannel; use Illuminate\Database\Eloquent\Model; trait HasNotificationSettings @@ -27,6 +28,7 @@ trait HasNotificationSettings 'discord' => $this->discordNotificationSettings, 'telegram' => $this->telegramNotificationSettings, 'slack' => $this->slackNotificationSettings, + 'pushover' => $this->pushoverNotificationSettings, default => null, }; } @@ -73,6 +75,7 @@ trait HasNotificationSettings 'discord' => DiscordChannel::class, 'telegram' => TelegramChannel::class, 'slack' => SlackChannel::class, + 'pushover' => PushoverChannel::class, ]; if ($event === 'general') { diff --git a/database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php b/database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php new file mode 100644 index 000000000..907f7a052 --- /dev/null +++ b/database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php @@ -0,0 +1,47 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + + $table->boolean('pushover_enabled')->default(false); + $table->text('pushover_user')->nullable(); + $table->text('pushover_token')->nullable(); + + $table->boolean('deployment_success_pushover_notifications')->default(false); + $table->boolean('deployment_failure_pushover_notifications')->default(true); + $table->boolean('status_change_pushover_notifications')->default(false); + $table->boolean('backup_success_pushover_notifications')->default(false); + $table->boolean('backup_failure_pushover_notifications')->default(true); + $table->boolean('scheduled_task_success_pushover_notifications')->default(false); + $table->boolean('scheduled_task_failure_pushover_notifications')->default(true); + $table->boolean('docker_cleanup_success_pushover_notifications')->default(false); + $table->boolean('docker_cleanup_failure_pushover_notifications')->default(true); + $table->boolean('server_disk_usage_pushover_notifications')->default(true); + $table->boolean('server_reachable_pushover_notifications')->default(false); + $table->boolean('server_unreachable_pushover_notifications')->default(true); + + $table->unique(['team_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('pushover_notification_settings'); + } +}; + diff --git a/resources/views/components/notification/navbar.blade.php b/resources/views/components/notification/navbar.blade.php index c4dbd25af..0193cf3cd 100644 --- a/resources/views/components/notification/navbar.blade.php +++ b/resources/views/components/notification/navbar.blade.php @@ -19,6 +19,10 @@ href="{{ route('notifications.slack') }}"> + + + - \ No newline at end of file + diff --git a/resources/views/livewire/notifications/pushover.blade.php b/resources/views/livewire/notifications/pushover.blade.php new file mode 100644 index 000000000..28a55516d --- /dev/null +++ b/resources/views/livewire/notifications/pushover.blade.php @@ -0,0 +1,84 @@ +
    + + Notifications | Coolify + + +
    +
    +

    Pushover

    + + Save + + @if ($pushoverEnabled) + + Send Test Notification + + @else + + Send Test Notification + + @endif +
    +
    + +
    + + + +

    Notification Settings

    +

    + Select events for which you would like to receive Pushover notifications. +

    +
    +
    +

    Deployments

    +
    + + + +
    +
    +
    +

    Backups

    +
    + + +
    +
    +
    +

    Scheduled Tasks

    +
    + + +
    +
    +
    +

    Server

    +
    + + + + + +
    +
    +
    +
    diff --git a/routes/web.php b/routes/web.php index 3570fe0ed..38ce7f15c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -14,6 +14,7 @@ use App\Livewire\Notifications\Discord as NotificationDiscord; use App\Livewire\Notifications\Email as NotificationEmail; use App\Livewire\Notifications\Slack as NotificationSlack; use App\Livewire\Notifications\Telegram as NotificationTelegram; +use App\Livewire\Notifications\Pushover as NotificationPushover; use App\Livewire\Profile\Index as ProfileIndex; use App\Livewire\Project\Application\Configuration as ApplicationConfiguration; use App\Livewire\Project\Application\Deployment\Index as DeploymentIndex; @@ -133,6 +134,7 @@ Route::middleware(['auth', 'verified'])->group(function () { Route::get('/telegram', NotificationTelegram::class)->name('notifications.telegram'); Route::get('/discord', NotificationDiscord::class)->name('notifications.discord'); Route::get('/slack', NotificationSlack::class)->name('notifications.slack'); + Route::get('/pushover', NotificationPushover::class)->name('notifications.pushover'); }); Route::prefix('storages')->group(function () { From 9afe9299217b2c946d0ae55c0247a366f60aa7d5 Mon Sep 17 00:00:00 2001 From: Zao Soula Date: Wed, 11 Dec 2024 18:37:12 +0100 Subject: [PATCH 17/84] refactor: unify notification icon --- app/Notifications/Dto/PushoverMessage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Notifications/Dto/PushoverMessage.php b/app/Notifications/Dto/PushoverMessage.php index 6022c2885..0efd1d526 100644 --- a/app/Notifications/Dto/PushoverMessage.php +++ b/app/Notifications/Dto/PushoverMessage.php @@ -17,8 +17,8 @@ class PushoverMessage { return match ($this->level) { 'info' => 'ℹ️', - 'error' => '🚨', - 'success' => '🟢', + 'error' => '❌', + 'success' => '✅ ', 'warning' => '⚠️', }; } From 9572c8498edb5831e44597d73b8aaf427f9e4624 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 18:42:55 +0100 Subject: [PATCH 18/84] chore: reorder navbar --- resources/views/components/notification/navbar.blade.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/views/components/notification/navbar.blade.php b/resources/views/components/notification/navbar.blade.php index 0193cf3cd..447a385c5 100644 --- a/resources/views/components/notification/navbar.blade.php +++ b/resources/views/components/notification/navbar.blade.php @@ -7,13 +7,13 @@ href="{{ route('notifications.email') }}"> - - - + + + From b48353c8a6ee644857f8b75cbd89f41a82614514 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 18:44:19 +0100 Subject: [PATCH 19/84] reorder more stuff --- app/Console/Commands/NotifyDemo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/NotifyDemo.php b/app/Console/Commands/NotifyDemo.php index b6e5015f8..990a03869 100644 --- a/app/Console/Commands/NotifyDemo.php +++ b/app/Console/Commands/NotifyDemo.php @@ -59,9 +59,9 @@ class NotifyDemo extends Command
    Channels:
    • email
    • -
    • slack
    • discord
    • telegram
    • +
    • slack
    • pushover
    @@ -73,6 +73,6 @@ class NotifyDemo extends Command
    In which manner you wish a coolified notification?
    - HTML, ['email', 'slack', 'discord', 'telegram', 'pushover']); + HTML, ['email', 'discord', 'telegram', 'slack', 'pushover']); } } From 1dade362534d0065ab3aff6aa50b39cbb0345e72 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:16:07 +0100 Subject: [PATCH 20/84] fix: slack helper --- resources/views/livewire/notifications/slack.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/notifications/slack.blade.php b/resources/views/livewire/notifications/slack.blade.php index 774f8e7e5..28bfe509f 100644 --- a/resources/views/livewire/notifications/slack.blade.php +++ b/resources/views/livewire/notifications/slack.blade.php @@ -24,7 +24,7 @@

    Notification Settings

    From 21ddaa317f247f38f5efda0b6caa1dca39a9e0cf Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:16:17 +0100 Subject: [PATCH 21/84] fix: telegram helper --- resources/views/livewire/notifications/telegram.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/livewire/notifications/telegram.blade.php b/resources/views/livewire/notifications/telegram.blade.php index 5dd859e9e..1e8bfcd92 100644 --- a/resources/views/livewire/notifications/telegram.blade.php +++ b/resources/views/livewire/notifications/telegram.blade.php @@ -26,8 +26,8 @@
    - +
    From 1568e55b050e47b2c75341412e13dd5499494d7f Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:16:28 +0100 Subject: [PATCH 22/84] fix: discord helper --- resources/views/livewire/notifications/discord.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/notifications/discord.blade.php b/resources/views/livewire/notifications/discord.blade.php index 1f83ed061..6caf31176 100644 --- a/resources/views/livewire/notifications/discord.blade.php +++ b/resources/views/livewire/notifications/discord.blade.php @@ -24,7 +24,7 @@

    Notification Settings

    From ebfc8a1094d5f114b5dc7af5262f682c925403a5 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:16:54 +0100 Subject: [PATCH 23/84] fix: telegram topic IDs --- app/Notifications/Channels/TelegramChannel.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/Notifications/Channels/TelegramChannel.php b/app/Notifications/Channels/TelegramChannel.php index 17d76181a..b5e7bf8b6 100644 --- a/app/Notifications/Channels/TelegramChannel.php +++ b/app/Notifications/Channels/TelegramChannel.php @@ -17,17 +17,24 @@ class TelegramChannel $chatId = $settings->telegram_chat_id; $topicId = match (get_class($notification)) { - \App\Notifications\Test::class => $settings->telegram_notifications_test_topic_id, + \App\Notifications\Application\DeploymentSuccess::class => $settings->telegram_notifications_deployment_success_topic_id, + \App\Notifications\Application\DeploymentFailed::class => $settings->telegram_notifications_deployment_failure_topic_id, \App\Notifications\Application\StatusChanged::class, \App\Notifications\Container\ContainerRestarted::class, \App\Notifications\Container\ContainerStopped::class => $settings->telegram_notifications_status_change_topic_id, - \App\Notifications\Application\DeploymentSuccess::class => $settings->telegram_notifications_deployment_success_topic_id, - \App\Notifications\Application\DeploymentFailed::class => $settings->telegram_notifications_deployment_failure_topic_id, + \App\Notifications\Database\BackupSuccess::class => $settings->telegram_notifications_backup_success_topic_id, \App\Notifications\Database\BackupFailed::class => $settings->telegram_notifications_backup_failure_topic_id, + + \App\Notifications\ScheduledTask\TaskSuccess::class => $settings->telegram_notifications_scheduled_task_success_topic_id, \App\Notifications\ScheduledTask\TaskFailed::class => $settings->telegram_notifications_scheduled_task_failure_topic_id, + + \App\Notifications\Server\DockerCleanupSuccess::class => $settings->telegram_notifications_docker_cleanup_success_topic_id, + \App\Notifications\Server\DockerCleanupFailed::class => $settings->telegram_notifications_docker_cleanup_failure_topic_id, + \App\Notifications\Server\HighDiskUsage::class => $settings->telegram_notifications_server_disk_usage_topic_id, \App\Notifications\Server\Unreachable::class => $settings->telegram_notifications_server_unreachable_topic_id, \App\Notifications\Server\Reachable::class => $settings->telegram_notifications_server_reachable_topic_id, + default => null, }; From 92fec9582cfe21ae01df0cefe25960f14c876567 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:18:39 +0100 Subject: [PATCH 24/84] fix: make pushover settings more clear - Rename User to User Key - Rename Token to API Token - fix: helper and docs links --- app/Livewire/Notifications/Pushover.php | 23 +++++++++---------- app/Models/PushoverNotificationSettings.php | 10 ++++---- app/Models/Team.php | 12 +++++----- .../Channels/PushoverChannel.php | 18 +++++++-------- ...e_pushover_notification_settings_table.php | 5 ++-- .../livewire/notifications/pushover.blade.php | 10 ++++---- 6 files changed, 38 insertions(+), 40 deletions(-) diff --git a/app/Livewire/Notifications/Pushover.php b/app/Livewire/Notifications/Pushover.php index 7a3b294a2..2e8205f5e 100644 --- a/app/Livewire/Notifications/Pushover.php +++ b/app/Livewire/Notifications/Pushover.php @@ -18,10 +18,10 @@ class Pushover extends Component public bool $pushoverEnabled = false; #[Validate(['string', 'nullable'])] - public ?string $pushoverToken = null; - + public ?string $pushoverUserKey = null; + #[Validate(['string', 'nullable'])] - public ?string $pushoverUser = null; + public ?string $pushoverApiToken = null; #[Validate(['boolean'])] public bool $deploymentSuccessPushoverNotifications = false; @@ -59,7 +59,6 @@ class Pushover extends Component #[Validate(['boolean'])] public bool $serverUnreachablePushoverNotifications = true; - public function mount() { try { @@ -76,8 +75,8 @@ class Pushover extends Component if ($toModel) { $this->validate(); $this->settings->pushover_enabled = $this->pushoverEnabled; - $this->settings->pushover_user = $this->pushoverUser; - $this->settings->pushover_token = $this->pushoverToken; + $this->settings->pushover_user_key_key = $this->pushoverUserKey; + $this->settings->pushover_api_token = $this->pushoverApiToken; $this->settings->deployment_success_pushover_notifications = $this->deploymentSuccessPushoverNotifications; $this->settings->deployment_failure_pushover_notifications = $this->deploymentFailurePushoverNotifications; @@ -96,8 +95,8 @@ class Pushover extends Component refreshSession(); } else { $this->pushoverEnabled = $this->settings->pushover_enabled; - $this->pushoverUser = $this->settings->pushover_user; - $this->pushoverToken = $this->settings->pushover_token; + $this->pushoverUserKey = $this->settings->pushover_user_key_key; + $this->pushoverApiToken = $this->settings->pushover_api_token; $this->deploymentSuccessPushoverNotifications = $this->settings->deployment_success_pushover_notifications; $this->deploymentFailurePushoverNotifications = $this->settings->deployment_failure_pushover_notifications; @@ -118,11 +117,11 @@ class Pushover extends Component { try { $this->validate([ - 'pushoverUser' => 'required', - 'pushoverToken' => 'required', + 'pushoverUserKey' => 'required', + 'pushoverApiToken' => 'required', ], [ - 'pushoverUser.required' => 'Pushover User is required.', - 'pushoverToken.required' => 'Pushover Token is required.', + 'pushoverUserKey.required' => 'Pushover User Key is required.', + 'pushoverApiToken.required' => 'Pushover API Token is required.', ]); $this->saveModel(); } catch (\Throwable $e) { diff --git a/app/Models/PushoverNotificationSettings.php b/app/Models/PushoverNotificationSettings.php index 7576a6040..7deeadedc 100644 --- a/app/Models/PushoverNotificationSettings.php +++ b/app/Models/PushoverNotificationSettings.php @@ -15,8 +15,8 @@ class PushoverNotificationSettings extends Model 'team_id', 'pushover_enabled', - 'pushover_user', - 'pushover_token', + 'pushover_user_key_key', + 'pushover_api_token', 'deployment_success_pushover_notifications', 'deployment_failure_pushover_notifications', @@ -29,12 +29,12 @@ class PushoverNotificationSettings extends Model 'server_disk_usage_pushover_notifications', 'server_reachable_pushover_notifications', 'server_unreachable_pushover_notifications', - ]; + ]; protected $casts = [ 'pushover_enabled' => 'boolean', - 'pushover_user' => 'encrypted', - 'pushover_token' => 'encrypted', + 'pushover_user_key_key' => 'encrypted', + 'pushover_api_token' => 'encrypted', 'deployment_success_pushover_notifications' => 'boolean', 'deployment_failure_pushover_notifications' => 'boolean', diff --git a/app/Models/Team.php b/app/Models/Team.php index fc10892a4..174137840 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -4,8 +4,8 @@ namespace App\Models; use App\Notifications\Channels\SendsDiscord; use App\Notifications\Channels\SendsEmail; -use App\Notifications\Channels\SendsSlack; use App\Notifications\Channels\SendsPushover; +use App\Notifications\Channels\SendsSlack; use App\Traits\HasNotificationSettings; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; @@ -33,7 +33,7 @@ use OpenApi\Attributes as OA; ] )] -class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack, SendsPushover +class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, SendsSlack { use HasNotificationSettings, Notifiable; @@ -156,12 +156,12 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack, SendsP { return data_get($this, 'slack_webhook_url', null); } - + public function routeNotificationForPushover() { return [ - 'user' => data_get($this, 'pushover_user', null), - 'token' => data_get($this, 'pushover_token', null), + 'user' => data_get($this, 'pushover_user_key_key', null), + 'token' => data_get($this, 'pushover_api_token', null), ]; } @@ -287,7 +287,7 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack, SendsP { return $this->hasOne(SlackNotificationSettings::class); } - + public function pushoverNotificationSettings() { return $this->hasOne(PushoverNotificationSettings::class); diff --git a/app/Notifications/Channels/PushoverChannel.php b/app/Notifications/Channels/PushoverChannel.php index 56d3cb03d..3d3728d01 100644 --- a/app/Notifications/Channels/PushoverChannel.php +++ b/app/Notifications/Channels/PushoverChannel.php @@ -7,15 +7,15 @@ use Illuminate\Notifications\Notification; class PushoverChannel { - public function send(SendsPushover $notifiable, Notification $notification): void - { - $message = $notification->toPushover(); - $pushoverSettings = $notifiable->pushoverNotificationSettings; + public function send(SendsPushover $notifiable, Notification $notification): void + { + $message = $notification->toPushover(); + $pushoverSettings = $notifiable->pushoverNotificationSettings; - if (! $pushoverSettings || ! $pushoverSettings->isEnabled() || ! $pushoverSettings->pushover_user || ! $pushoverSettings->pushover_token) { - return; + if (! $pushoverSettings || ! $pushoverSettings->isEnabled() || ! $pushoverSettings->pushover_user_key || ! $pushoverSettings->pushover_api_token) { + return; + } + + SendMessageToPushoverJob::dispatch($message, $pushoverSettings->pushover_api_token, $pushoverSettings->pushover_user_key); } - - SendMessageToPushoverJob::dispatch($message, $pushoverSettings->pushover_token, $pushoverSettings->pushover_user); - } } diff --git a/database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php b/database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php index 907f7a052..8c317d997 100644 --- a/database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php +++ b/database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php @@ -16,8 +16,8 @@ return new class extends Migration $table->foreignId('team_id')->constrained()->cascadeOnDelete(); $table->boolean('pushover_enabled')->default(false); - $table->text('pushover_user')->nullable(); - $table->text('pushover_token')->nullable(); + $table->text('pushover_user_key')->nullable(); + $table->text('pushover_api_token')->nullable(); $table->boolean('deployment_success_pushover_notifications')->default(false); $table->boolean('deployment_failure_pushover_notifications')->default(true); @@ -44,4 +44,3 @@ return new class extends Migration Schema::dropIfExists('pushover_notification_settings'); } }; - diff --git a/resources/views/livewire/notifications/pushover.blade.php b/resources/views/livewire/notifications/pushover.blade.php index 28a55516d..271af5367 100644 --- a/resources/views/livewire/notifications/pushover.blade.php +++ b/resources/views/livewire/notifications/pushover.blade.php @@ -23,12 +23,12 @@
    - + helper="Get your User Key in Pushover. You need to be logged in to Pushover to see your user key in the top right corner.
    Pushover Dashboard" required + id="pushoverUserKey" label="User Key" /> +

    Notification Settings

    From 866abfb1d41699e5860f7448b4270078f0f462c9 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:31:25 +0100 Subject: [PATCH 25/84] fix: typo in pushover user key --- app/Models/PushoverNotificationSettings.php | 4 ++-- app/Models/Team.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Models/PushoverNotificationSettings.php b/app/Models/PushoverNotificationSettings.php index 7deeadedc..e3191dcc3 100644 --- a/app/Models/PushoverNotificationSettings.php +++ b/app/Models/PushoverNotificationSettings.php @@ -15,7 +15,7 @@ class PushoverNotificationSettings extends Model 'team_id', 'pushover_enabled', - 'pushover_user_key_key', + 'pushover_user_key', 'pushover_api_token', 'deployment_success_pushover_notifications', @@ -33,7 +33,7 @@ class PushoverNotificationSettings extends Model protected $casts = [ 'pushover_enabled' => 'boolean', - 'pushover_user_key_key' => 'encrypted', + 'pushover_user_key' => 'encrypted', 'pushover_api_token' => 'encrypted', 'deployment_success_pushover_notifications' => 'boolean', diff --git a/app/Models/Team.php b/app/Models/Team.php index 174137840..e55cb0d19 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -160,7 +160,7 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen public function routeNotificationForPushover() { return [ - 'user' => data_get($this, 'pushover_user_key_key', null), + 'user' => data_get($this, 'pushover_user_key', null), 'token' => data_get($this, 'pushover_api_token', null), ]; } From 48ddc65435e1561e7ee442159dd46c9385524acb Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:34:44 +0100 Subject: [PATCH 26/84] fix: use Livewire refresh method and lock properties --- app/Livewire/Notifications/Email.php | 2 ++ app/Livewire/Notifications/Pushover.php | 17 +++++++++++++---- app/Livewire/Notifications/Slack.php | 9 +++++++++ app/Livewire/Notifications/Telegram.php | 10 +++++++++- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php index dc2a95e84..eac48cf77 100644 --- a/app/Livewire/Notifications/Email.php +++ b/app/Livewire/Notifications/Email.php @@ -14,8 +14,10 @@ class Email extends Component { protected $listeners = ['refresh' => '$refresh']; + #[Locked] public Team $team; + #[Locked] public EmailNotificationSettings $settings; #[Locked] diff --git a/app/Livewire/Notifications/Pushover.php b/app/Livewire/Notifications/Pushover.php index 2e8205f5e..f1e4c464d 100644 --- a/app/Livewire/Notifications/Pushover.php +++ b/app/Livewire/Notifications/Pushover.php @@ -5,22 +5,27 @@ namespace App\Livewire\Notifications; use App\Models\PushoverNotificationSettings; use App\Models\Team; use App\Notifications\Test; +use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Component; class Pushover extends Component { + protected $listeners = ['refresh' => '$refresh']; + + #[Locked] public Team $team; + #[Locked] public PushoverNotificationSettings $settings; #[Validate(['boolean'])] public bool $pushoverEnabled = false; - #[Validate(['string', 'nullable'])] + #[Validate(['nullable', 'string'])] public ?string $pushoverUserKey = null; - #[Validate(['string', 'nullable'])] + #[Validate(['nullable', 'string'])] public ?string $pushoverApiToken = null; #[Validate(['boolean'])] @@ -75,7 +80,7 @@ class Pushover extends Component if ($toModel) { $this->validate(); $this->settings->pushover_enabled = $this->pushoverEnabled; - $this->settings->pushover_user_key_key = $this->pushoverUserKey; + $this->settings->pushover_user_key = $this->pushoverUserKey; $this->settings->pushover_api_token = $this->pushoverApiToken; $this->settings->deployment_success_pushover_notifications = $this->deploymentSuccessPushoverNotifications; @@ -95,7 +100,7 @@ class Pushover extends Component refreshSession(); } else { $this->pushoverEnabled = $this->settings->pushover_enabled; - $this->pushoverUserKey = $this->settings->pushover_user_key_key; + $this->pushoverUserKey = $this->settings->pushover_user_key; $this->pushoverApiToken = $this->settings->pushover_api_token; $this->deploymentSuccessPushoverNotifications = $this->settings->deployment_success_pushover_notifications; @@ -128,6 +133,8 @@ class Pushover extends Component $this->pushoverEnabled = false; return handleError($e, $this); + } finally { + $this->dispatch('refresh'); } } @@ -137,6 +144,8 @@ class Pushover extends Component $this->syncData(true); } catch (\Throwable $e) { return handleError($e, $this); + } finally { + $this->dispatch('refresh'); } } diff --git a/app/Livewire/Notifications/Slack.php b/app/Livewire/Notifications/Slack.php index 97464fa1c..f47ad8a97 100644 --- a/app/Livewire/Notifications/Slack.php +++ b/app/Livewire/Notifications/Slack.php @@ -5,13 +5,18 @@ namespace App\Livewire\Notifications; use App\Models\SlackNotificationSettings; use App\Models\Team; use App\Notifications\Test; +use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Component; class Slack extends Component { + protected $listeners = ['refresh' => '$refresh']; + + #[Locked] public Team $team; + #[Locked] public SlackNotificationSettings $settings; #[Validate(['boolean'])] @@ -121,6 +126,8 @@ class Slack extends Component $this->slackEnabled = false; return handleError($e, $this); + } finally { + $this->dispatch('refresh'); } } @@ -130,6 +137,8 @@ class Slack extends Component $this->syncData(true); } catch (\Throwable $e) { return handleError($e, $this); + } finally { + $this->dispatch('refresh'); } } diff --git a/app/Livewire/Notifications/Telegram.php b/app/Livewire/Notifications/Telegram.php index de2fa9cdc..85c16b277 100644 --- a/app/Livewire/Notifications/Telegram.php +++ b/app/Livewire/Notifications/Telegram.php @@ -5,13 +5,18 @@ namespace App\Livewire\Notifications; use App\Models\Team; use App\Models\TelegramNotificationSettings; use App\Notifications\Test; +use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Component; class Telegram extends Component { + protected $listeners = ['refresh' => '$refresh']; + + #[Locked] public Team $team; + #[Locked] public TelegramNotificationSettings $settings; #[Validate(['boolean'])] @@ -141,7 +146,6 @@ class Telegram extends Component $this->settings->telegram_notifications_server_unreachable_topic_id = $this->telegramNotificationsServerUnreachableTopicId; $this->settings->save(); - refreshSession(); } else { $this->telegramEnabled = $this->settings->telegram_enabled; $this->telegramToken = $this->settings->telegram_token; @@ -181,6 +185,8 @@ class Telegram extends Component $this->syncData(true); } catch (\Throwable $e) { return handleError($e, $this); + } finally { + $this->dispatch('refresh'); } } @@ -210,6 +216,8 @@ class Telegram extends Component $this->telegramEnabled = false; return handleError($e, $this); + } finally { + $this->dispatch('refresh'); } } From a7330fe52b90bcc29475f1f8099a28ca805c062c Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:43:58 +0100 Subject: [PATCH 27/84] fix: create pushover settings for existing teams --- ...014_create_slack_notification_settings_table.php | 3 ++- ..._create_pushover_notification_settings_table.php | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/database/migrations/2024_12_06_142014_create_slack_notification_settings_table.php b/database/migrations/2024_12_06_142014_create_slack_notification_settings_table.php index 790e0f667..8aee40d87 100644 --- a/database/migrations/2024_12_06_142014_create_slack_notification_settings_table.php +++ b/database/migrations/2024_12_06_142014_create_slack_notification_settings_table.php @@ -3,6 +3,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Schema; return new class extends Migration @@ -42,7 +43,7 @@ return new class extends Migration 'team_id' => $team->id, ]); } catch (\Throwable $e) { - \Log::error('Error migrating slack notification settings from teams table: '.$e->getMessage()); + Log::error('Error creating slack notification settings for existing teams: '.$e->getMessage()); } } } diff --git a/database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php b/database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php index 8c317d997..ad4215a07 100644 --- a/database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php +++ b/database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php @@ -2,6 +2,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Schema; return new class extends Migration @@ -34,6 +36,17 @@ return new class extends Migration $table->unique(['team_id']); }); + $teams = DB::table('teams')->get(); + + foreach ($teams as $team) { + try { + DB::table('pushover_notification_settings')->insert([ + 'team_id' => $team->id, + ]); + } catch (\Throwable $e) { + Log::error('Error creating pushover notification settings for existing teams: '.$e->getMessage()); + } + } } /** From d2ff8c652170aeff36e9bda055503ffffb6c3ed9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 11 Dec 2024 21:31:12 +0100 Subject: [PATCH 28/84] feat: add seeder command and configuration for database seeding --- app/Console/Commands/Seeder.php | 24 +++++++++++++++++++ config/constants.php | 4 ++++ .../etc/s6-overlay/s6-rc.d/init-seeder/up | 5 +--- 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 app/Console/Commands/Seeder.php diff --git a/app/Console/Commands/Seeder.php b/app/Console/Commands/Seeder.php new file mode 100644 index 000000000..e37b6a9d2 --- /dev/null +++ b/app/Console/Commands/Seeder.php @@ -0,0 +1,24 @@ +info('Seeder is enabled on this server.'); + $this->call('db:seed', ['--class' => 'ProductionSeeder', '--force' => true]); + exit(0); + } else { + $this->info('Seeder is disabled on this server.'); + exit(0); + } + } +} diff --git a/config/constants.php b/config/constants.php index 3054df1f0..a02d6616a 100644 --- a/config/constants.php +++ b/config/constants.php @@ -37,6 +37,10 @@ return [ 'is_migration_enabled' => env('MIGRATION_ENABLED', true), ], + 'seeder' => [ + 'is_seeder_enabled' => env('SEEDER_ENABLED', true), + ], + 'horizon' => [ 'is_horizon_enabled' => env('HORIZON_ENABLED', true), 'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true), diff --git a/docker/production/etc/s6-overlay/s6-rc.d/init-seeder/up b/docker/production/etc/s6-overlay/s6-rc.d/init-seeder/up index 20d17c58b..007f4233e 100644 --- a/docker/production/etc/s6-overlay/s6-rc.d/init-seeder/up +++ b/docker/production/etc/s6-overlay/s6-rc.d/init-seeder/up @@ -6,10 +6,7 @@ cd /var/www/html foreground { php artisan - db:seed - --class - ProductionSeeder - --force + start:seeder } From 09746aa87774be069ce5fbda258f7f6181e4cd1d Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 11 Dec 2024 21:38:31 +0100 Subject: [PATCH 29/84] fix: update token permission check from 'write' to 'root' --- .../migrations/2024_10_30_074601_rename_token_permissions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2024_10_30_074601_rename_token_permissions.php b/database/migrations/2024_10_30_074601_rename_token_permissions.php index 2ca98d090..69d774d7f 100644 --- a/database/migrations/2024_10_30_074601_rename_token_permissions.php +++ b/database/migrations/2024_10_30_074601_rename_token_permissions.php @@ -40,7 +40,7 @@ return new class extends Migration $tokens = PersonalAccessToken::all(); foreach ($tokens as $token) { $abilities = collect(); - if (in_array('write', $token->abilities)) { + if (in_array('root', $token->abilities)) { $abilities->push('*'); } else { if (in_array('read', $token->abilities)) { From f88438edd3b531f3ff21b12695a7b296de4f7a2f Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 11 Dec 2024 23:13:13 +0100 Subject: [PATCH 30/84] chore: rename topicID to threadId like in the telegram API response --- app/Jobs/SendMessageToTelegramJob.php | 6 +- app/Livewire/Notifications/Telegram.php | 72 +++++++++---------- app/Models/TelegramNotificationSettings.php | 44 ++++++------ .../Channels/TelegramChannel.php | 28 ++++---- ...e_telegram_notification_settings_table.php | 24 +++---- ...notification_settings_from_teams_table.php | 27 +++---- .../livewire/notifications/telegram.blade.php | 48 ++++++------- 7 files changed, 125 insertions(+), 124 deletions(-) diff --git a/app/Jobs/SendMessageToTelegramJob.php b/app/Jobs/SendMessageToTelegramJob.php index 85f4fc934..6b0a64ae3 100644 --- a/app/Jobs/SendMessageToTelegramJob.php +++ b/app/Jobs/SendMessageToTelegramJob.php @@ -32,7 +32,7 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue public array $buttons, public string $token, public string $chatId, - public ?string $topicId = null, + public ?string $threadId = null, ) { $this->onQueue('high'); } @@ -67,8 +67,8 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue 'chat_id' => $this->chatId, 'text' => $this->text, ]; - if ($this->topicId) { - $payload['message_thread_id'] = $this->topicId; + if ($this->threadId) { + $payload['message_thread_id'] = $this->threadId; } $response = Http::post($url, $payload); if ($response->failed()) { diff --git a/app/Livewire/Notifications/Telegram.php b/app/Livewire/Notifications/Telegram.php index 85c16b277..01afb4ac6 100644 --- a/app/Livewire/Notifications/Telegram.php +++ b/app/Livewire/Notifications/Telegram.php @@ -65,40 +65,40 @@ class Telegram extends Component public bool $serverUnreachableTelegramNotifications = true; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsDeploymentSuccessTopicId = null; + public ?string $telegramNotificationsDeploymentSuccessThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsDeploymentFailureTopicId = null; + public ?string $telegramNotificationsDeploymentFailureThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsStatusChangeTopicId = null; + public ?string $telegramNotificationsStatusChangeThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsBackupSuccessTopicId = null; + public ?string $telegramNotificationsBackupSuccessThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsBackupFailureTopicId = null; + public ?string $telegramNotificationsBackupFailureThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsScheduledTaskSuccessTopicId = null; + public ?string $telegramNotificationsScheduledTaskSuccessThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsScheduledTaskFailureTopicId = null; + public ?string $telegramNotificationsScheduledTaskFailureThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsDockerCleanupSuccessTopicId = null; + public ?string $telegramNotificationsDockerCleanupSuccessThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsDockerCleanupFailureTopicId = null; + public ?string $telegramNotificationsDockerCleanupFailureThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsServerDiskUsageTopicId = null; + public ?string $telegramNotificationsServerDiskUsageThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsServerReachableTopicId = null; + public ?string $telegramNotificationsServerReachableThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsServerUnreachableTopicId = null; + public ?string $telegramNotificationsServerUnreachableThreadId = null; public function mount() { @@ -132,18 +132,18 @@ class Telegram extends Component $this->settings->server_reachable_telegram_notifications = $this->serverReachableTelegramNotifications; $this->settings->server_unreachable_telegram_notifications = $this->serverUnreachableTelegramNotifications; - $this->settings->telegram_notifications_deployment_success_topic_id = $this->telegramNotificationsDeploymentSuccessTopicId; - $this->settings->telegram_notifications_deployment_failure_topic_id = $this->telegramNotificationsDeploymentFailureTopicId; - $this->settings->telegram_notifications_status_change_topic_id = $this->telegramNotificationsStatusChangeTopicId; - $this->settings->telegram_notifications_backup_success_topic_id = $this->telegramNotificationsBackupSuccessTopicId; - $this->settings->telegram_notifications_backup_failure_topic_id = $this->telegramNotificationsBackupFailureTopicId; - $this->settings->telegram_notifications_scheduled_task_success_topic_id = $this->telegramNotificationsScheduledTaskSuccessTopicId; - $this->settings->telegram_notifications_scheduled_task_failure_topic_id = $this->telegramNotificationsScheduledTaskFailureTopicId; - $this->settings->telegram_notifications_docker_cleanup_success_topic_id = $this->telegramNotificationsDockerCleanupSuccessTopicId; - $this->settings->telegram_notifications_docker_cleanup_failure_topic_id = $this->telegramNotificationsDockerCleanupFailureTopicId; - $this->settings->telegram_notifications_server_disk_usage_topic_id = $this->telegramNotificationsServerDiskUsageTopicId; - $this->settings->telegram_notifications_server_reachable_topic_id = $this->telegramNotificationsServerReachableTopicId; - $this->settings->telegram_notifications_server_unreachable_topic_id = $this->telegramNotificationsServerUnreachableTopicId; + $this->settings->telegram_notifications_deployment_success_thread_id = $this->telegramNotificationsDeploymentSuccessThreadId; + $this->settings->telegram_notifications_deployment_failure_thread_id = $this->telegramNotificationsDeploymentFailureThreadId; + $this->settings->telegram_notifications_status_change_thread_id = $this->telegramNotificationsStatusChangeThreadId; + $this->settings->telegram_notifications_backup_success_thread_id = $this->telegramNotificationsBackupSuccessThreadId; + $this->settings->telegram_notifications_backup_failure_thread_id = $this->telegramNotificationsBackupFailureThreadId; + $this->settings->telegram_notifications_scheduled_task_success_thread_id = $this->telegramNotificationsScheduledTaskSuccessThreadId; + $this->settings->telegram_notifications_scheduled_task_failure_thread_id = $this->telegramNotificationsScheduledTaskFailureThreadId; + $this->settings->telegram_notifications_docker_cleanup_success_thread_id = $this->telegramNotificationsDockerCleanupSuccessThreadId; + $this->settings->telegram_notifications_docker_cleanup_failure_thread_id = $this->telegramNotificationsDockerCleanupFailureThreadId; + $this->settings->telegram_notifications_server_disk_usage_thread_id = $this->telegramNotificationsServerDiskUsageThreadId; + $this->settings->telegram_notifications_server_reachable_thread_id = $this->telegramNotificationsServerReachableThreadId; + $this->settings->telegram_notifications_server_unreachable_thread_id = $this->telegramNotificationsServerUnreachableThreadId; $this->settings->save(); } else { @@ -164,18 +164,18 @@ class Telegram extends Component $this->serverReachableTelegramNotifications = $this->settings->server_reachable_telegram_notifications; $this->serverUnreachableTelegramNotifications = $this->settings->server_unreachable_telegram_notifications; - $this->telegramNotificationsDeploymentSuccessTopicId = $this->settings->telegram_notifications_deployment_success_topic_id; - $this->telegramNotificationsDeploymentFailureTopicId = $this->settings->telegram_notifications_deployment_failure_topic_id; - $this->telegramNotificationsStatusChangeTopicId = $this->settings->telegram_notifications_status_change_topic_id; - $this->telegramNotificationsBackupSuccessTopicId = $this->settings->telegram_notifications_backup_success_topic_id; - $this->telegramNotificationsBackupFailureTopicId = $this->settings->telegram_notifications_backup_failure_topic_id; - $this->telegramNotificationsScheduledTaskSuccessTopicId = $this->settings->telegram_notifications_scheduled_task_success_topic_id; - $this->telegramNotificationsScheduledTaskFailureTopicId = $this->settings->telegram_notifications_scheduled_task_failure_topic_id; - $this->telegramNotificationsDockerCleanupSuccessTopicId = $this->settings->telegram_notifications_docker_cleanup_success_topic_id; - $this->telegramNotificationsDockerCleanupFailureTopicId = $this->settings->telegram_notifications_docker_cleanup_failure_topic_id; - $this->telegramNotificationsServerDiskUsageTopicId = $this->settings->telegram_notifications_server_disk_usage_topic_id; - $this->telegramNotificationsServerReachableTopicId = $this->settings->telegram_notifications_server_reachable_topic_id; - $this->telegramNotificationsServerUnreachableTopicId = $this->settings->telegram_notifications_server_unreachable_topic_id; + $this->telegramNotificationsDeploymentSuccessThreadId = $this->settings->telegram_notifications_deployment_success_thread_id; + $this->telegramNotificationsDeploymentFailureThreadId = $this->settings->telegram_notifications_deployment_failure_thread_id; + $this->telegramNotificationsStatusChangeThreadId = $this->settings->telegram_notifications_status_change_thread_id; + $this->telegramNotificationsBackupSuccessThreadId = $this->settings->telegram_notifications_backup_success_thread_id; + $this->telegramNotificationsBackupFailureThreadId = $this->settings->telegram_notifications_backup_failure_thread_id; + $this->telegramNotificationsScheduledTaskSuccessThreadId = $this->settings->telegram_notifications_scheduled_task_success_thread_id; + $this->telegramNotificationsScheduledTaskFailureThreadId = $this->settings->telegram_notifications_scheduled_task_failure_thread_id; + $this->telegramNotificationsDockerCleanupSuccessThreadId = $this->settings->telegram_notifications_docker_cleanup_success_thread_id; + $this->telegramNotificationsDockerCleanupFailureThreadId = $this->settings->telegram_notifications_docker_cleanup_failure_thread_id; + $this->telegramNotificationsServerDiskUsageThreadId = $this->settings->telegram_notifications_server_disk_usage_thread_id; + $this->telegramNotificationsServerReachableThreadId = $this->settings->telegram_notifications_server_reachable_thread_id; + $this->telegramNotificationsServerUnreachableThreadId = $this->settings->telegram_notifications_server_unreachable_thread_id; } } diff --git a/app/Models/TelegramNotificationSettings.php b/app/Models/TelegramNotificationSettings.php index 2edca14ff..78bd841bd 100644 --- a/app/Models/TelegramNotificationSettings.php +++ b/app/Models/TelegramNotificationSettings.php @@ -30,17 +30,17 @@ class TelegramNotificationSettings extends Model 'server_reachable_telegram_notifications', 'server_unreachable_telegram_notifications', - 'telegram_notifications_deployment_success_topic_id', - 'telegram_notifications_deployment_failure_topic_id', - 'telegram_notifications_status_change_topic_id', - 'telegram_notifications_backup_success_topic_id', - 'telegram_notifications_backup_failure_topic_id', - 'telegram_notifications_scheduled_task_success_topic_id', - 'telegram_notifications_scheduled_task_failure_topic_id', - 'telegram_notifications_docker_cleanup_topic_id', - 'telegram_notifications_server_disk_usage_topic_id', - 'telegram_notifications_server_reachable_topic_id', - 'telegram_notifications_server_unreachable_topic_id', + 'telegram_notifications_deployment_success_thread_id', + 'telegram_notifications_deployment_failure_thread_id', + 'telegram_notifications_status_change_thread_id', + 'telegram_notifications_backup_success_thread_id', + 'telegram_notifications_backup_failure_thread_id', + 'telegram_notifications_scheduled_task_success_thread_id', + 'telegram_notifications_scheduled_task_failure_thread_id', + 'telegram_notifications_docker_cleanup_thread_id', + 'telegram_notifications_server_disk_usage_thread_id', + 'telegram_notifications_server_reachable_thread_id', + 'telegram_notifications_server_unreachable_thread_id', ]; protected $casts = [ @@ -60,17 +60,17 @@ class TelegramNotificationSettings extends Model 'server_reachable_telegram_notifications' => 'boolean', 'server_unreachable_telegram_notifications' => 'boolean', - 'telegram_notifications_deployment_success_topic_id' => 'encrypted', - 'telegram_notifications_deployment_failure_topic_id' => 'encrypted', - 'telegram_notifications_status_change_topic_id' => 'encrypted', - 'telegram_notifications_backup_success_topic_id' => 'encrypted', - 'telegram_notifications_backup_failure_topic_id' => 'encrypted', - 'telegram_notifications_scheduled_task_success_topic_id' => 'encrypted', - 'telegram_notifications_scheduled_task_failure_topic_id' => 'encrypted', - 'telegram_notifications_docker_cleanup_topic_id' => 'encrypted', - 'telegram_notifications_server_disk_usage_topic_id' => 'encrypted', - 'telegram_notifications_server_reachable_topic_id' => 'encrypted', - 'telegram_notifications_server_unreachable_topic_id' => 'encrypted', + 'telegram_notifications_deployment_success_thread_id' => 'encrypted', + 'telegram_notifications_deployment_failure_thread_id' => 'encrypted', + 'telegram_notifications_status_change_thread_id' => 'encrypted', + 'telegram_notifications_backup_success_thread_id' => 'encrypted', + 'telegram_notifications_backup_failure_thread_id' => 'encrypted', + 'telegram_notifications_scheduled_task_success_thread_id' => 'encrypted', + 'telegram_notifications_scheduled_task_failure_thread_id' => 'encrypted', + 'telegram_notifications_docker_cleanup_thread_id' => 'encrypted', + 'telegram_notifications_server_disk_usage_thread_id' => 'encrypted', + 'telegram_notifications_server_reachable_thread_id' => 'encrypted', + 'telegram_notifications_server_unreachable_thread_id' => 'encrypted', ]; public function team() diff --git a/app/Notifications/Channels/TelegramChannel.php b/app/Notifications/Channels/TelegramChannel.php index b5e7bf8b6..ea4ab7171 100644 --- a/app/Notifications/Channels/TelegramChannel.php +++ b/app/Notifications/Channels/TelegramChannel.php @@ -16,24 +16,24 @@ class TelegramChannel $telegramToken = $settings->telegram_token; $chatId = $settings->telegram_chat_id; - $topicId = match (get_class($notification)) { - \App\Notifications\Application\DeploymentSuccess::class => $settings->telegram_notifications_deployment_success_topic_id, - \App\Notifications\Application\DeploymentFailed::class => $settings->telegram_notifications_deployment_failure_topic_id, + $threadId = match (get_class($notification)) { + \App\Notifications\Application\DeploymentSuccess::class => $settings->telegram_notifications_deployment_success_thread_id, + \App\Notifications\Application\DeploymentFailed::class => $settings->telegram_notifications_deployment_failure_thread_id, \App\Notifications\Application\StatusChanged::class, \App\Notifications\Container\ContainerRestarted::class, - \App\Notifications\Container\ContainerStopped::class => $settings->telegram_notifications_status_change_topic_id, + \App\Notifications\Container\ContainerStopped::class => $settings->telegram_notifications_status_change_thread_id, - \App\Notifications\Database\BackupSuccess::class => $settings->telegram_notifications_backup_success_topic_id, - \App\Notifications\Database\BackupFailed::class => $settings->telegram_notifications_backup_failure_topic_id, + \App\Notifications\Database\BackupSuccess::class => $settings->telegram_notifications_backup_success_thread_id, + \App\Notifications\Database\BackupFailed::class => $settings->telegram_notifications_backup_failure_thread_id, - \App\Notifications\ScheduledTask\TaskSuccess::class => $settings->telegram_notifications_scheduled_task_success_topic_id, - \App\Notifications\ScheduledTask\TaskFailed::class => $settings->telegram_notifications_scheduled_task_failure_topic_id, + \App\Notifications\ScheduledTask\TaskSuccess::class => $settings->telegram_notifications_scheduled_task_success_thread_id, + \App\Notifications\ScheduledTask\TaskFailed::class => $settings->telegram_notifications_scheduled_task_failure_thread_id, - \App\Notifications\Server\DockerCleanupSuccess::class => $settings->telegram_notifications_docker_cleanup_success_topic_id, - \App\Notifications\Server\DockerCleanupFailed::class => $settings->telegram_notifications_docker_cleanup_failure_topic_id, - \App\Notifications\Server\HighDiskUsage::class => $settings->telegram_notifications_server_disk_usage_topic_id, - \App\Notifications\Server\Unreachable::class => $settings->telegram_notifications_server_unreachable_topic_id, - \App\Notifications\Server\Reachable::class => $settings->telegram_notifications_server_reachable_topic_id, + \App\Notifications\Server\DockerCleanupSuccess::class => $settings->telegram_notifications_docker_cleanup_success_thread_id, + \App\Notifications\Server\DockerCleanupFailed::class => $settings->telegram_notifications_docker_cleanup_failure_thread_id, + \App\Notifications\Server\HighDiskUsage::class => $settings->telegram_notifications_server_disk_usage_thread_id, + \App\Notifications\Server\Unreachable::class => $settings->telegram_notifications_server_unreachable_thread_id, + \App\Notifications\Server\Reachable::class => $settings->telegram_notifications_server_reachable_thread_id, default => null, }; @@ -42,6 +42,6 @@ class TelegramChannel return; } - SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $topicId); + SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $threadId); } } diff --git a/database/migrations/2024_12_05_212440_create_telegram_notification_settings_table.php b/database/migrations/2024_12_05_212440_create_telegram_notification_settings_table.php index 190d148fc..db4d998e3 100644 --- a/database/migrations/2024_12_05_212440_create_telegram_notification_settings_table.php +++ b/database/migrations/2024_12_05_212440_create_telegram_notification_settings_table.php @@ -32,18 +32,18 @@ return new class extends Migration $table->boolean('server_reachable_telegram_notifications')->default(false); $table->boolean('server_unreachable_telegram_notifications')->default(true); - $table->text('telegram_notifications_deployment_success_topic_id')->nullable(); - $table->text('telegram_notifications_deployment_failure_topic_id')->nullable(); - $table->text('telegram_notifications_status_change_topic_id')->nullable(); - $table->text('telegram_notifications_backup_success_topic_id')->nullable(); - $table->text('telegram_notifications_backup_failure_topic_id')->nullable(); - $table->text('telegram_notifications_scheduled_task_success_topic_id')->nullable(); - $table->text('telegram_notifications_scheduled_task_failure_topic_id')->nullable(); - $table->text('telegram_notifications_docker_cleanup_success_topic_id')->nullable(); - $table->text('telegram_notifications_docker_cleanup_failure_topic_id')->nullable(); - $table->text('telegram_notifications_server_disk_usage_topic_id')->nullable(); - $table->text('telegram_notifications_server_reachable_topic_id')->nullable(); - $table->text('telegram_notifications_server_unreachable_topic_id')->nullable(); + $table->text('telegram_notifications_deployment_success_thread_id')->nullable(); + $table->text('telegram_notifications_deployment_failure_thread_id')->nullable(); + $table->text('telegram_notifications_status_change_thread_id')->nullable(); + $table->text('telegram_notifications_backup_success_thread_id')->nullable(); + $table->text('telegram_notifications_backup_failure_thread_id')->nullable(); + $table->text('telegram_notifications_scheduled_task_success_thread_id')->nullable(); + $table->text('telegram_notifications_scheduled_task_failure_thread_id')->nullable(); + $table->text('telegram_notifications_docker_cleanup_success_thread_id')->nullable(); + $table->text('telegram_notifications_docker_cleanup_failure_thread_id')->nullable(); + $table->text('telegram_notifications_server_disk_usage_thread_id')->nullable(); + $table->text('telegram_notifications_server_reachable_thread_id')->nullable(); + $table->text('telegram_notifications_server_unreachable_thread_id')->nullable(); $table->unique(['team_id']); }); diff --git a/database/migrations/2024_12_05_212705_migrate_telegram_notification_settings_from_teams_table.php b/database/migrations/2024_12_05_212705_migrate_telegram_notification_settings_from_teams_table.php index 0c10646b9..df120e273 100644 --- a/database/migrations/2024_12_05_212705_migrate_telegram_notification_settings_from_teams_table.php +++ b/database/migrations/2024_12_05_212705_migrate_telegram_notification_settings_from_teams_table.php @@ -4,6 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Schema; return new class extends Migration @@ -30,17 +31,17 @@ return new class extends Migration 'status_change_telegram_notifications' => $team->telegram_notifications_status_changes ?? false, 'server_disk_usage_telegram_notifications' => $team->telegram_notifications_server_disk_usage ?? true, - 'telegram_notifications_deployment_success_topic_id' => $team->telegram_notifications_deployments_message_thread_id ? Crypt::encryptString($team->telegram_notifications_deployments_message_thread_id) : null, - 'telegram_notifications_deployment_failure_topic_id' => $team->telegram_notifications_deployments_message_thread_id ? Crypt::encryptString($team->telegram_notifications_deployments_message_thread_id) : null, - 'telegram_notifications_backup_success_topic_id' => $team->telegram_notifications_database_backups_message_thread_id ? Crypt::encryptString($team->telegram_notifications_database_backups_message_thread_id) : null, - 'telegram_notifications_backup_failure_topic_id' => $team->telegram_notifications_database_backups_message_thread_id ? Crypt::encryptString($team->telegram_notifications_database_backups_message_thread_id) : null, - 'telegram_notifications_scheduled_task_success_topic_id' => $team->telegram_notifications_scheduled_tasks_thread_id ? Crypt::encryptString($team->telegram_notifications_scheduled_tasks_thread_id) : null, - 'telegram_notifications_scheduled_task_failure_topic_id' => $team->telegram_notifications_scheduled_tasks_thread_id ? Crypt::encryptString($team->telegram_notifications_scheduled_tasks_thread_id) : null, - 'telegram_notifications_status_change_topic_id' => $team->telegram_notifications_status_changes_message_thread_id ? Crypt::encryptString($team->telegram_notifications_status_changes_message_thread_id) : null, + 'telegram_notifications_deployment_success_thread_id' => $team->telegram_notifications_deployments_message_thread_id ? Crypt::encryptString($team->telegram_notifications_deployments_message_thread_id) : null, + 'telegram_notifications_deployment_failure_thread_id' => $team->telegram_notifications_deployments_message_thread_id ? Crypt::encryptString($team->telegram_notifications_deployments_message_thread_id) : null, + 'telegram_notifications_backup_success_thread_id' => $team->telegram_notifications_database_backups_message_thread_id ? Crypt::encryptString($team->telegram_notifications_database_backups_message_thread_id) : null, + 'telegram_notifications_backup_failure_thread_id' => $team->telegram_notifications_database_backups_message_thread_id ? Crypt::encryptString($team->telegram_notifications_database_backups_message_thread_id) : null, + 'telegram_notifications_scheduled_task_success_thread_id' => $team->telegram_notifications_scheduled_tasks_thread_id ? Crypt::encryptString($team->telegram_notifications_scheduled_tasks_thread_id) : null, + 'telegram_notifications_scheduled_task_failure_thread_id' => $team->telegram_notifications_scheduled_tasks_thread_id ? Crypt::encryptString($team->telegram_notifications_scheduled_tasks_thread_id) : null, + 'telegram_notifications_status_change_thread_id' => $team->telegram_notifications_status_changes_message_thread_id ? Crypt::encryptString($team->telegram_notifications_status_changes_message_thread_id) : null, ] ); } catch (Exception $e) { - \Log::error('Error migrating telegram notification settings from teams table: '.$e->getMessage()); + Log::error('Error migrating telegram notification settings from teams table: '.$e->getMessage()); } } @@ -101,13 +102,13 @@ return new class extends Migration 'telegram_notifications_scheduled_tasks' => $setting->scheduled_task_success_telegram_notifications || $setting->scheduled_task_failure_telegram_notifications, 'telegram_notifications_server_disk_usage' => $setting->server_disk_usage_telegram_notifications, - 'telegram_notifications_deployments_message_thread_id' => $setting->telegram_notifications_deployment_success_topic_id ? Crypt::decryptString($setting->telegram_notifications_deployment_success_topic_id) : null, - 'telegram_notifications_status_changes_message_thread_id' => $setting->telegram_notifications_status_change_topic_id ? Crypt::decryptString($setting->telegram_notifications_status_change_topic_id) : null, - 'telegram_notifications_database_backups_message_thread_id' => $setting->telegram_notifications_backup_success_topic_id ? Crypt::decryptString($setting->telegram_notifications_backup_success_topic_id) : null, - 'telegram_notifications_scheduled_tasks_thread_id' => $setting->telegram_notifications_scheduled_task_success_topic_id ? Crypt::decryptString($setting->telegram_notifications_scheduled_task_success_topic_id) : null, + 'telegram_notifications_deployments_message_thread_id' => $setting->telegram_notifications_deployment_success_thread_id ? Crypt::decryptString($setting->telegram_notifications_deployment_success_thread_id) : null, + 'telegram_notifications_status_changes_message_thread_id' => $setting->telegram_notifications_status_change_thread_id ? Crypt::decryptString($setting->telegram_notifications_status_change_thread_id) : null, + 'telegram_notifications_database_backups_message_thread_id' => $setting->telegram_notifications_backup_success_thread_id ? Crypt::decryptString($setting->telegram_notifications_backup_success_thread_id) : null, + 'telegram_notifications_scheduled_tasks_thread_id' => $setting->telegram_notifications_scheduled_task_success_thread_id ? Crypt::decryptString($setting->telegram_notifications_scheduled_task_success_thread_id) : null, ]); } catch (Exception $e) { - \Log::error('Error migrating telegram notification settings from teams table: '.$e->getMessage()); + Log::error('Error migrating telegram notification settings from teams table: '.$e->getMessage()); } } } diff --git a/resources/views/livewire/notifications/telegram.blade.php b/resources/views/livewire/notifications/telegram.blade.php index 1e8bfcd92..ecc9360fa 100644 --- a/resources/views/livewire/notifications/telegram.blade.php +++ b/resources/views/livewire/notifications/telegram.blade.php @@ -44,16 +44,16 @@ - +

    - +
    @@ -61,8 +61,8 @@ label="Container Status Changes" helper="Send a notification when a container status changes. It will send a notification for Stopped and Restarted events of a container." />
    - +
    @@ -74,8 +74,8 @@ - +
    @@ -83,8 +83,8 @@
    - + @@ -97,8 +97,8 @@ - +
    @@ -106,8 +106,8 @@
    - + @@ -120,8 +120,8 @@ - +
    @@ -129,8 +129,8 @@
    - +
    @@ -138,8 +138,8 @@
    - +
    @@ -147,8 +147,8 @@
    - + @@ -157,8 +157,8 @@ - + From 390f304b141270e12842ae25b1ea176bc288534d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Pires?= Date: Thu, 12 Dec 2024 00:46:12 -0300 Subject: [PATCH 31/84] feat: add documenso service --- public/svgs/documenso.png | Bin 0 -> 10539 bytes templates/compose/documenso.yaml | 44 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 public/svgs/documenso.png create mode 100644 templates/compose/documenso.yaml diff --git a/public/svgs/documenso.png b/public/svgs/documenso.png new file mode 100644 index 0000000000000000000000000000000000000000..baa63653234c5bb0f0e654f88e45edb1d00312d5 GIT binary patch literal 10539 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 Date: Thu, 12 Dec 2024 08:40:34 +0100 Subject: [PATCH 32/84] fix: pushover --- .../livewire/notifications/pushover.blade.php | 140 +++++++++--------- routes/web.php | 2 +- 2 files changed, 72 insertions(+), 70 deletions(-) diff --git a/resources/views/livewire/notifications/pushover.blade.php b/resources/views/livewire/notifications/pushover.blade.php index 271af5367..c8dd777d8 100644 --- a/resources/views/livewire/notifications/pushover.blade.php +++ b/resources/views/livewire/notifications/pushover.blade.php @@ -1,84 +1,86 @@
    Notifications | Coolify - - -
    -
    -

    Pushover

    - - Save - - @if ($pushoverEnabled) + + + +
    +

    Pushover

    + + Save + + @if ($pushoverEnabled) Send Test Notification - @else + @else Send Test Notification - @endif -
    -
    - -
    + @endif +
    +
    + +
    +
    + helper="Get your User Key in Pushover. You need to be logged in to Pushover to see your user key in the top right corner.
    Pushover Dashboard" + required id="pushoverUserKey" label="User Key" /> - -

    Notification Settings

    -

    - Select events for which you would like to receive Pushover notifications. -

    -
    -
    -

    Deployments

    -
    - - - -
    -
    -
    -

    Backups

    -
    - - -
    -
    -
    -

    Scheduled Tasks

    -
    - - -
    -
    -
    -

    Server

    -
    - - - - - -
    + helper="Generate an API Token/Key in Pushover by creating a new application.
    Create Pushover Application" + required id="pushoverApiToken" label="API Token" /> +
    + +

    Notification Settings

    +

    + Select events for which you would like to receive Pushover notifications. +

    +
    +
    +

    Deployments

    +
    + + +
    +
    +

    Backups

    +
    + + +
    +
    +
    +

    Scheduled Tasks

    +
    + + +
    +
    +
    +

    Server

    +
    + + + + + +
    +
    +
    diff --git a/routes/web.php b/routes/web.php index 38ce7f15c..3d12b9b46 100644 --- a/routes/web.php +++ b/routes/web.php @@ -12,9 +12,9 @@ use App\Livewire\Destination\Show as DestinationShow; use App\Livewire\ForcePasswordReset; use App\Livewire\Notifications\Discord as NotificationDiscord; use App\Livewire\Notifications\Email as NotificationEmail; +use App\Livewire\Notifications\Pushover as NotificationPushover; use App\Livewire\Notifications\Slack as NotificationSlack; use App\Livewire\Notifications\Telegram as NotificationTelegram; -use App\Livewire\Notifications\Pushover as NotificationPushover; use App\Livewire\Profile\Index as ProfileIndex; use App\Livewire\Project\Application\Configuration as ApplicationConfiguration; use App\Livewire\Project\Application\Deployment\Index as DeploymentIndex; From f0812b754ceb5cefcdd1da240fa78f3c9a7adf02 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 Dec 2024 08:40:38 +0100 Subject: [PATCH 33/84] add new sponsors --- README.md | 7 ++++++- other/logos/internetgarden.ico | Bin 0 -> 15406 bytes 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 other/logos/internetgarden.ico diff --git a/README.md b/README.md index dac48d127..4d93a07cf 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,9 @@ Special thanks to our biggest sponsors! * [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase. * [GoldenVM](https://billing.goldenvm.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes. * [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management. -* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies. +* [Cloudify.ro](https://cloudify.ro/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes. +* [Syntaxfm](https://syntax.fm/?ref=coolify.io) - Podcast for web developers. +* [PFGlabs](https://pfglabs.com/?ref=coolify.io) - Build real project with Golang. * [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets. * [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers. * [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities. @@ -92,6 +94,9 @@ Special thanks to our biggest sponsors! Michael Mazurczak Formbricks StartupFame +jyc.dev +BitLaunch +Internet Garden Jonas Jaeger JP Evercam diff --git a/other/logos/internetgarden.ico b/other/logos/internetgarden.ico new file mode 100644 index 0000000000000000000000000000000000000000..e555984be39b8a20ff9b2a3b8b13a87261cfa976 GIT binary patch literal 15406 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 Date: Thu, 12 Dec 2024 08:41:28 +0100 Subject: [PATCH 34/84] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d93a07cf..56edffd31 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,8 @@ Special thanks to our biggest sponsors! ### Special Sponsors -![image](https://github.com/user-attachments/assets/726fb63e-c3b8-4260-b3ac-06780605ec5d) +![image](https://github.com/user-attachments/assets/6022bc9c-8435-4d14-9497-8be230ed8cb1) + * [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry. * [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions. From 2e1f9b51191f64d0a5e19de9572ac998f1a2eeff Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 Dec 2024 09:00:58 +0100 Subject: [PATCH 35/84] refactor: remove unused Azure and Authentik service configurations from services.php --- config/services.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/config/services.php b/config/services.php index 46fd12ec3..509e73756 100644 --- a/config/services.php +++ b/config/services.php @@ -30,19 +30,4 @@ return [ 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], - - 'azure' => [ - 'client_id' => env('AZURE_CLIENT_ID'), - 'client_secret' => env('AZURE_CLIENT_SECRET'), - 'redirect' => env('AZURE_REDIRECT_URI'), - 'tenant' => env('AZURE_TENANT_ID'), - 'proxy' => env('AZURE_PROXY'), - ], - - 'authentik' => [ - 'base_url' => env('AUTHENTIK_BASE_URL'), - 'client_id' => env('AUTHENTIK_CLIENT_ID'), - 'client_secret' => env('AUTHENTIK_CLIENT_SECRET'), - 'redirect' => env('AUTHENTIK_REDIRECT_URI'), - ], ]; From fba7f6f585a34aefefa1b5c97c34392265aff1c7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 Dec 2024 10:03:31 +0100 Subject: [PATCH 36/84] refactor: change email column types in instance_settings migration from string to text --- ..._122142_encrypt_instance_settings_email_columns.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/database/migrations/2024_12_10_122142_encrypt_instance_settings_email_columns.php b/database/migrations/2024_12_10_122142_encrypt_instance_settings_email_columns.php index 44e0f2f47..5602e0ae9 100644 --- a/database/migrations/2024_12_10_122142_encrypt_instance_settings_email_columns.php +++ b/database/migrations/2024_12_10_122142_encrypt_instance_settings_email_columns.php @@ -45,11 +45,11 @@ return new class extends Migration public function down(): void { Schema::table('instance_settings', function (Blueprint $table) { - $table->string('smtp_from_address')->nullable()->change(); - $table->string('smtp_from_name')->nullable()->change(); - $table->string('smtp_recipients')->nullable()->change(); - $table->string('smtp_host')->nullable()->change(); - $table->string('smtp_username')->nullable()->change(); + $table->text('smtp_from_address')->nullable()->change(); + $table->text('smtp_from_name')->nullable()->change(); + $table->text('smtp_recipients')->nullable()->change(); + $table->text('smtp_host')->nullable()->change(); + $table->text('smtp_username')->nullable()->change(); }); if (DB::table('instance_settings')->exists()) { From 2053fed8a78c4f01de6c3da3289390a02534da45 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 Dec 2024 10:04:12 +0100 Subject: [PATCH 37/84] fix: oauth seeder --- database/seeders/OauthSettingSeeder.php | 49 ++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/database/seeders/OauthSettingSeeder.php b/database/seeders/OauthSettingSeeder.php index 2b733120c..e004d1450 100644 --- a/database/seeders/OauthSettingSeeder.php +++ b/database/seeders/OauthSettingSeeder.php @@ -12,19 +12,56 @@ class OauthSettingSeeder extends Seeder */ public function run(): void { - $providers = [ + $providers = collect([ 'azure', 'bitbucket', 'github', 'gitlab', 'google', 'authentik', - ]; + ]); - foreach ($providers as $provider) { - OauthSetting::updateOrCreate( - ['provider' => $provider] - ); + $isOauthSeeded = OauthSetting::count() > 0; + $isOauthWithZeroId = OauthSetting::where('id', 0)->exists(); + if ($isOauthSeeded) { + if ($isOauthWithZeroId) { + $allProviders = OauthSetting::all(); + $notFoundProviders = $providers->diff($allProviders->pluck('provider')); + + $allProviders->each(function ($provider) use ($providers) { + $provider->delete(); + $providerName = $provider->provider; + + $foundProvider = $providers->first(function ($provider) use ($providerName) { + return $provider === $providerName; + }); + + if ($foundProvider) { + $newProvder = new OauthSetting; + $newProvder = $provider; + unset($newProvder->id); + $newProvder->save(); + } + }); + + foreach ($notFoundProviders as $provider) { + OauthSetting::create([ + 'provider' => $provider, + ]); + } + } else { + foreach ($providers as $provider) { + OauthSetting::updateOrCreate([ + 'provider' => $provider, + ]); + } + } + } else { + foreach ($providers as $provider) { + OauthSetting::create([ + 'provider' => $provider, + ]); + } } } } From 78eb956870e8adcb413c5d7292de1b47ef2affb7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 Dec 2024 10:11:46 +0100 Subject: [PATCH 38/84] fix: correct heading display for OAuth settings in settings-oauth.blade.php --- resources/views/livewire/settings-oauth.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/settings-oauth.blade.php b/resources/views/livewire/settings-oauth.blade.php index fcb806547..a525cf7b2 100644 --- a/resources/views/livewire/settings-oauth.blade.php +++ b/resources/views/livewire/settings-oauth.blade.php @@ -16,7 +16,7 @@
    @foreach ($oauth_settings_map as $oauth_setting)
    -

    {{ ucfirst($oauth_setting->provider) }} Oauth

    +

    {{ ucfirst($oauth_setting->provider) }}

    From 76b44e2e6a48bea27ee58be928195ad36bc9c1da Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 Dec 2024 11:06:00 +0100 Subject: [PATCH 39/84] feat: add new password magic env with symbols --- bootstrap/helpers/shared.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index ea051c84e..7aa411cd1 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -824,6 +824,12 @@ function generateEnvValue(string $command, Service|Application|null $service = n case 'PASSWORD_64': $generatedValue = Str::password(length: 64, symbols: false); break; + case 'PASSWORDWITHSYMBOLS': + $generatedValue = Str::password(symbols: true); + break; + case 'PASSWORDWITHSYMBOLS_64': + $generatedValue = Str::password(length: 64, symbols: true); + break; // This is not base64, it's just a random string case 'BASE64_64': $generatedValue = Str::random(64); From b2344aeb2fd3d40c362e12fff2ae50ae0e51ac72 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 Dec 2024 11:06:19 +0100 Subject: [PATCH 40/84] add: checkmate service and fix prowlar slogan (too long) --- public/svgs/checkmate.png | Bin 0 -> 4867 bytes templates/compose/checkmate.yaml | 46 +++++++++++++++++++++++++++++++ templates/compose/prowlarr.yaml | 2 +- templates/service-templates.json | 16 ++++++++++- 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 public/svgs/checkmate.png create mode 100644 templates/compose/checkmate.yaml diff --git a/public/svgs/checkmate.png b/public/svgs/checkmate.png new file mode 100644 index 0000000000000000000000000000000000000000..f0f31466b2afbb1c2bb7a301f714c248384da89c GIT binary patch literal 4867 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 N<|fw0)rRhI{{fcS#kT+e literal 0 HcmV?d00001 diff --git a/templates/compose/checkmate.yaml b/templates/compose/checkmate.yaml new file mode 100644 index 000000000..91018b3b9 --- /dev/null +++ b/templates/compose/checkmate.yaml @@ -0,0 +1,46 @@ +# documentation: https://bluewavelabs.gitbook.io/checkmate +# slogan: An open source server monitoring application +# tags: monitoring,server,uptime,healthcheck +# logo: svgs/checkmate.png +# port: 80 + +services: + client: + image: bluewaveuptime/uptime_client:latest + environment: + - SERVICE_FQDN_CHECKMATE_80 + - UPTIME_APP_API_BASE_URL=${SERVICE_FQDN_CHECKMATESERVER_5000}/api/v1 + depends_on: + - server + server: + image: bluewaveuptime/uptime_server:latest + environment: + - SERVICE_FQDN_CHECKMATESERVER_5000 + - JWT_SECRET=${SERVICE_PASSWORD_64_JWT} + - REFRESH_TOKEN_SECRET=${SERVICE_PASSWORD_64_REFRESH} + - SYSTEM_EMAIL_ADDRESS=${SYSTEM_EMAIL_ADDRESS:-test@example.com} + - SYSTEM_EMAIL_PASSWORD=${SERVICE_PASSWORD_64_EMAIL} + - SYSTEM_EMAIL_HOST=${SYSTEM_EMAIL_HOST} + - SYSTEM_EMAIL_PORT=${SYSTEM_EMAIL_PORT} + - PAGESPEED_API_KEY=${PAGESPEED_API_KEY} + - DB_CONNECTION_STRING=${DB_CONNECTION_STRING:-mongodb://mongodb:27017/uptime_db} + - REDIS_HOST=${REDIS_HOST:-redis} + - REDIS_PORT=${REDIS_PORT:-6379} + - DB_TYPE=${DB_TYPE:-MongoDB} + - TOKEN_TTL=${TOKEN_TTL:-99d} + - REFRESH_TOKEN_TTL=${REFRESH_TOKEN_TTL:-99d} + volumes: + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + - redis + - mongodb + redis: + image: bluewaveuptime/uptime_redis:latest + volumes: + - redis:/data + mongodb: + image: bluewaveuptime/uptime_database_mongo:latest + volumes: + - mongodb:/data/db + command: ["mongod", "--quiet"] + diff --git a/templates/compose/prowlarr.yaml b/templates/compose/prowlarr.yaml index f173ee1ca..0bcb42c5e 100644 --- a/templates/compose/prowlarr.yaml +++ b/templates/compose/prowlarr.yaml @@ -1,5 +1,5 @@ # documentation: https://hub.docker.com/r/linuxserver/prowlarr -# slogan: Prowlarr⁠ is a indexer manager/proxy built on the popular arr .net/reactjs base stack to integrate with your various PVR apps. Prowlarr supports both Torrent Trackers and Usenet Indexers. It integrates seamlessly with Sonarr, Radarr, Lidarr, and Readarr offering complete management of your indexers with no per app Indexer setup required (we do it all). +# slogan: Prowlarr⁠ is a indexer manager/proxy built on the popular arr .net/reactjs base stack to integrate with your various PVR apps. # tags: media, server, movies, tv, indexer, torrent, nzb, usenet # logo: svgs/prowlarr.svg # port: 9696 diff --git a/templates/service-templates.json b/templates/service-templates.json index 7bedcbf5c..6c8bc4564 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -338,6 +338,20 @@ "minversion": "0.0.0", "port": "3000" }, + "checkmate": { + "documentation": "https://bluewavelabs.gitbook.io/checkmate?utm_source=coolify.io", + "slogan": "An open source server monitoring application", + "compose": "c2VydmljZXM6CiAgY2xpZW50OgogICAgaW1hZ2U6ICdibHVld2F2ZXVwdGltZS91cHRpbWVfY2xpZW50OmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9DSEVDS01BVEVfODAKICAgICAgLSAnVVBUSU1FX0FQUF9BUElfQkFTRV9VUkw9JHtTRVJWSUNFX0ZRRE5fQ0hFQ0tNQVRFU0VSVkVSXzUwMDB9L2FwaS92MScKICAgIGRlcGVuZHNfb246CiAgICAgIC0gc2VydmVyCiAgc2VydmVyOgogICAgaW1hZ2U6ICdibHVld2F2ZXVwdGltZS91cHRpbWVfc2VydmVyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9DSEVDS01BVEVTRVJWRVJfNTAwMAogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF82NF9KV1R9JwogICAgICAtICdSRUZSRVNIX1RPS0VOX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfNjRfUkVGUkVTSH0nCiAgICAgIC0gJ1NZU1RFTV9FTUFJTF9BRERSRVNTPSR7U1lTVEVNX0VNQUlMX0FERFJFU1M6LXRlc3RAZXhhbXBsZS5jb219JwogICAgICAtICdTWVNURU1fRU1BSUxfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0VNQUlMfScKICAgICAgLSAnU1lTVEVNX0VNQUlMX0hPU1Q9JHtTWVNURU1fRU1BSUxfSE9TVH0nCiAgICAgIC0gJ1NZU1RFTV9FTUFJTF9QT1JUPSR7U1lTVEVNX0VNQUlMX1BPUlR9JwogICAgICAtICdQQUdFU1BFRURfQVBJX0tFWT0ke1BBR0VTUEVFRF9BUElfS0VZfScKICAgICAgLSAnREJfQ09OTkVDVElPTl9TVFJJTkc9JHtEQl9DT05ORUNUSU9OX1NUUklORzotbW9uZ29kYjovL21vbmdvZGI6MjcwMTcvdXB0aW1lX2RifScKICAgICAgLSAnUkVESVNfSE9TVD0ke1JFRElTX0hPU1Q6LXJlZGlzfScKICAgICAgLSAnUkVESVNfUE9SVD0ke1JFRElTX1BPUlQ6LTYzNzl9JwogICAgICAtICdEQl9UWVBFPSR7REJfVFlQRTotTW9uZ29EQn0nCiAgICAgIC0gJ1RPS0VOX1RUTD0ke1RPS0VOX1RUTDotOTlkfScKICAgICAgLSAnUkVGUkVTSF9UT0tFTl9UVEw9JHtSRUZSRVNIX1RPS0VOX1RUTDotOTlkfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJy92YXIvcnVuL2RvY2tlci5zb2NrOi92YXIvcnVuL2RvY2tlci5zb2NrJwogICAgZGVwZW5kc19vbjoKICAgICAgLSByZWRpcwogICAgICAtIG1vbmdvZGIKICByZWRpczoKICAgIGltYWdlOiAnYmx1ZXdhdmV1cHRpbWUvdXB0aW1lX3JlZGlzOmxhdGVzdCcKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3JlZGlzOi9kYXRhJwogIG1vbmdvZGI6CiAgICBpbWFnZTogJ2JsdWV3YXZldXB0aW1lL3VwdGltZV9kYXRhYmFzZV9tb25nbzpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICdtb25nb2RiOi9kYXRhL2RiJwogICAgY29tbWFuZDoKICAgICAgLSBtb25nb2QKICAgICAgLSAnLS1xdWlldCcK", + "tags": [ + "monitoring", + "server", + "uptime", + "healthcheck" + ], + "logo": "svgs/checkmate.png", + "minversion": "0.0.0", + "port": "80" + }, "classicpress-with-mariadb": { "documentation": "https://www.classicpress.net/?utm_source=coolify.io", "slogan": "A lightweight, stable, instantly familiar free open-source content management system, based on WordPress without the block editor (Gutenberg).", @@ -2335,7 +2349,7 @@ }, "prowlarr": { "documentation": "https://hub.docker.com/r/linuxserver/prowlarr?utm_source=coolify.io", - "slogan": "Prowlarr\u2060 is a indexer manager/proxy built on the popular arr .net/reactjs base stack to integrate with your various PVR apps. Prowlarr supports both Torrent Trackers and Usenet Indexers. It integrates seamlessly with Sonarr, Radarr, Lidarr, and Readarr offering complete management of your indexers with no per app Indexer setup required (we do it all).", + "slogan": "Prowlarr\u2060 is a indexer manager/proxy built on the popular arr .net/reactjs base stack to integrate with your various PVR apps.", "compose": "c2VydmljZXM6CiAgcHJvd2xhcnI6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvcHJvd2xhcnI6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1BST1dMQVJSXzk2OTYKICAgICAgLSBfQVBQX1VSTD0kU0VSVklDRV9GUUROX1BST1dMQVJSCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gJ1RaPSR7VFo6LUFtZXJpY2EvVG9yb250b30nCiAgICB2b2x1bWVzOgogICAgICAtICdwcm93bGFyci1jb25maWc6L2NvbmZpZycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo5Njk2L3BpbmcnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK", "tags": [ "media", From ab819c6b9dd3aee62cef4c51b1b2a25802ac97d4 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 Dec 2024 11:36:50 +0100 Subject: [PATCH 41/84] fix: adjust spacing in login form for improved layout --- resources/views/auth/login.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index d6c3edf84..9404ed2c5 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -12,7 +12,7 @@ @endforeach
    @endif -
    +
    @csrf @env('local') From 458cd96ac8667e033564f69017d669e4c8ab9fdc Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 Dec 2024 13:20:13 +0100 Subject: [PATCH 42/84] fix: services env values should be sensitive --- app/Http/Controllers/Api/ServicesController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index e6b7e9854..ed9af8c90 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -25,6 +25,8 @@ class ServicesController extends Controller $service->makeHidden([ 'docker_compose_raw', 'docker_compose', + 'value', + 'real_value', ]); } From 4e47485971aac4c9f11aa28022d73728a8d5fe0c Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 12 Dec 2024 13:51:31 +0100 Subject: [PATCH 43/84] fix: documenso --- templates/compose/documenso.yaml | 34 +++++++++++++++----------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/templates/compose/documenso.yaml b/templates/compose/documenso.yaml index a6cab9d3d..38b6b4f74 100644 --- a/templates/compose/documenso.yaml +++ b/templates/compose/documenso.yaml @@ -6,19 +6,19 @@ services: documenso: - image: "documenso/documenso" + image: documenso/documenso depends_on: database: condition: service_healthy environment: - SERVICE_FQDN_DOCUMENSO_3000 - - NEXTAUTH_URL=$SERVICE_FQDN_DOCUMENSO_3000 - - NEXTAUTH_SECRET=$SERVICE_BASE64_AUTHSECRET - - NEXT_PRIVATE_ENCRYPTION_KEY=$SERVICE_BASE64_ENCRYPTIONKEY - - NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY=$SERVICE_BASE64_SECONDARYENCRYPTIONKEY - - NEXT_PUBLIC_WEBAPP_URL=$SERVICE_FQDN_DOCUMENSO_3000 - - NEXT_PRIVATE_DATABASE_URL=postgresql://$SERVICE_USER_POSTGRES:$SERVICE_PASSWORD_POSTGRES@database/documenso?schema=public - - NEXT_PRIVATE_DIRECT_DATABASE_URL=postgresql://$SERVICE_USER_POSTGRES:$SERVICE_PASSWORD_POSTGRES@database/documenso?schema=public + - NEXTAUTH_URL=${SERVICE_FQDN_DOCUMENSO_3000} + - NEXTAUTH_SECRET=${SERVICE_BASE64_AUTHSECRET} + - NEXT_PRIVATE_ENCRYPTION_KEY=${SERVICE_BASE64_ENCRYPTIONKEY} + - NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY=${SERVICE_BASE64_SECONDARYENCRYPTIONKEY} + - NEXT_PUBLIC_WEBAPP_URL=${SERVICE_FQDN_DOCUMENSO_3000} + - NEXT_PRIVATE_DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@database/${POSTGRES_DB:-documenso-db}?schema=public + - NEXT_PRIVATE_DIRECT_DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@database/${POSTGRES_DB:-documenso-db}?schema=public healthcheck: test: - CMD-SHELL @@ -28,17 +28,15 @@ services: retries: 20 database: - image: "postgres:16-alpine" + image: postgres:16-alpine environment: - - POSTGRES_USER=$SERVICE_USER_POSTGRES - - POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES - - POSTGRES_DB=documenso + - POSTGRES_USER=${SERVICE_USER_POSTGRES} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + - POSTGRES_DB=${POSTGRES_DB:-documenso-db} volumes: - - "postgresql-data:/var/lib/postgresql/data" + - documenso_postgresql_data:/var/lib/postgresql/data healthcheck: - test: - - CMD-SHELL - - "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}" + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 5s - timeout: 10s - retries: 20 + timeout: 20s + retries: 10 From 48155fa2b747eeed0ce1d2a57af4ecbb88b40ae1 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 12 Dec 2024 13:53:03 +0100 Subject: [PATCH 44/84] fix: dolibarr --- templates/compose/dolibarr.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/templates/compose/dolibarr.yaml b/templates/compose/dolibarr.yaml index 7d32d7009..6c32d0f92 100644 --- a/templates/compose/dolibarr.yaml +++ b/templates/compose/dolibarr.yaml @@ -12,7 +12,7 @@ services: - WWW_USER_ID=${WWW_USER_ID:-1000} - WWW_GROUP_ID=${WWW_GROUP_ID:-1000} - DOLI_DB_HOST=mariadb - - DOLI_DB_NAME=${MYSQL_DATABASE:-dolidb} + - DOLI_DB_NAME=${MYSQL_DATABASE:-dolibarr-db} - DOLI_DB_USER=${SERVICE_USER_MYSQL} - DOLI_DB_PASSWORD=${SERVICE_PASSWORD_MYSQL} - DOLI_URL_ROOT=${SERVICE_URL_DOLIBARR} @@ -26,15 +26,16 @@ services: interval: 2s timeout: 10s retries: 15 + mariadb: image: mariadb:latest environment: - - MYSQL_DATABASE=${MYSQL_DATABASE:-dolidb} + - MYSQL_DATABASE=${MYSQL_DATABASE:-dolibarr-db} - MYSQL_USER=${SERVICE_USER_MYSQL} - MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL} - MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_ROOT} volumes: - - mariadb-data:/var/lib/mysql + - dolibarr_mariadb_data:/var/lib/mysql healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 5s From 223aaadba3e5b931459dfaa3c0205427c6b4162b Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 12 Dec 2024 13:55:29 +0100 Subject: [PATCH 45/84] Update service-templates.json --- templates/service-templates.json | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/templates/service-templates.json b/templates/service-templates.json index 6c8bc4564..19db89d52 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -537,6 +537,25 @@ "minversion": "0.0.0", "port": "3000" }, + "documenso": { + "documentation": "https://docs.documenso.com/?utm_source=coolify.io", + "slogan": "Document signing, finally open source", + "compose": "c2VydmljZXM6CiAgZG9jdW1lbnNvOgogICAgaW1hZ2U6IGRvY3VtZW5zby9kb2N1bWVuc28KICAgIGRlcGVuZHNfb246CiAgICAgIGRhdGFiYXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fRE9DVU1FTlNPXzMwMDAKICAgICAgLSAnTkVYVEFVVEhfVVJMPSR7U0VSVklDRV9GUUROX0RPQ1VNRU5TT18zMDAwfScKICAgICAgLSAnTkVYVEFVVEhfU0VDUkVUPSR7U0VSVklDRV9CQVNFNjRfQVVUSFNFQ1JFVH0nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9FTkNSWVBUSU9OX0tFWT0ke1NFUlZJQ0VfQkFTRTY0X0VOQ1JZUFRJT05LRVl9JwogICAgICAtICdORVhUX1BSSVZBVEVfRU5DUllQVElPTl9TRUNPTkRBUllfS0VZPSR7U0VSVklDRV9CQVNFNjRfU0VDT05EQVJZRU5DUllQVElPTktFWX0nCiAgICAgIC0gJ05FWFRfUFVCTElDX1dFQkFQUF9VUkw9JHtTRVJWSUNFX0ZRRE5fRE9DVU1FTlNPXzMwMDB9JwogICAgICAtICdORVhUX1BSSVZBVEVfREFUQUJBU0VfVVJMPXBvc3RncmVzcWw6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBkYXRhYmFzZS8ke1BPU1RHUkVTX0RCOi1kb2N1bWVuc28tZGJ9P3NjaGVtYT1wdWJsaWMnCiAgICAgIC0gJ05FWFRfUFJJVkFURV9ESVJFQ1RfREFUQUJBU0VfVVJMPXBvc3RncmVzcWw6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBkYXRhYmFzZS8ke1BPU1RHUkVTX0RCOi1kb2N1bWVuc28tZGJ9P3NjaGVtYT1wdWJsaWMnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gIndnZXQgLXEgLU8gLSBodHRwOi8vZG9jdW1lbnNvOjMwMDAvIHwgZ3JlcCAtcSAnU2lnbiBpbiB0byB5b3VyIGFjY291bnQnIgogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDIwCiAgZGF0YWJhc2U6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotZG9jdW1lbnNvLWRifScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2RvY3VtZW5zb19wb3N0Z3Jlc3FsX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAo=", + "tags": [ + "signing", + "opensource", + "document", + "pdf", + "e-signature", + "digital-signature", + "document-signing", + "pdf-signing", + "docusign" + ], + "logo": "svgs/documenso.png", + "minversion": "0.0.0", + "port": "3000" + }, "docuseal-with-postgres": { "documentation": "https://www.docuseal.co/?utm_source=coolify.io", "slogan": "Document Signing for Everyone free forever for individuals, extensible for businesses and developers. Open Source Alternative to DocuSign, PandaDoc and more.", @@ -572,6 +591,18 @@ "logo": "svgs/dokuwiki.png", "minversion": "0.0.0" }, + "dolibarr": { + "documentation": "https://www.dolibarr.org/documentation-home.php?utm_source=coolify.io", + "slogan": "Dolibarr is a modern software package to manage your organization's activity (contacts, quotes, invoices, orders, stocks, agenda, hr, expense reports, accountancy, ecm, manufacturing, ...).", + "compose": "c2VydmljZXM6CiAgZG9saWJhcnI6CiAgICBpbWFnZTogJ2RvbGliYXJyL2RvbGliYXJyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9ET0xJQkFSUl84MAogICAgICAtICdXV1dfVVNFUl9JRD0ke1dXV19VU0VSX0lEOi0xMDAwfScKICAgICAgLSAnV1dXX0dST1VQX0lEPSR7V1dXX0dST1VQX0lEOi0xMDAwfScKICAgICAgLSBET0xJX0RCX0hPU1Q9bWFyaWFkYgogICAgICAtICdET0xJX0RCX05BTUU9JHtNWVNRTF9EQVRBQkFTRTotZG9saWJhcnItZGJ9JwogICAgICAtICdET0xJX0RCX1VTRVI9JHtTRVJWSUNFX1VTRVJfTVlTUUx9JwogICAgICAtICdET0xJX0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NWVNRTH0nCiAgICAgIC0gJ0RPTElfVVJMX1JPT1Q9JHtTRVJWSUNFX1VSTF9ET0xJQkFSUn0nCiAgICAgIC0gJ0RPTElfQURNSU5fTE9HSU49JHtTRVJWSUNFX1VTRVJfRE9MSUJBUlJ9JwogICAgICAtICdET0xJX0FETUlOX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9ET0xJQkFSUn0nCiAgICAgIC0gJ0RPTElfQ1JPTj0ke0RPTElfQ1JPTjotMH0nCiAgICAgIC0gJ0RPTElfSU5JVF9ERU1PPSR7RE9MSV9JTklUX0RFTU86LTB9JwogICAgICAtICdET0xJX0NPTVBBTllfTkFNRT0ke0RPTElfQ09NUEFOWV9OQU1FOi1NeUJpZ0NvbXBhbnl9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgbWFyaWFkYjoKICAgIGltYWdlOiAnbWFyaWFkYjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTVlTUUxfREFUQUJBU0U9JHtNWVNRTF9EQVRBQkFTRTotZG9saWJhcnItZGJ9JwogICAgICAtICdNWVNRTF9VU0VSPSR7U0VSVklDRV9VU0VSX01ZU1FMfScKICAgICAgLSAnTVlTUUxfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX01ZU1FMfScKICAgICAgLSAnTVlTUUxfUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUk9PVH0nCiAgICB2b2x1bWVzOgogICAgICAtICdkb2xpYmFycl9tYXJpYWRiX2RhdGE6L3Zhci9saWIvbXlzcWwnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gaGVhbHRoY2hlY2suc2gKICAgICAgICAtICctLWNvbm5lY3QnCiAgICAgICAgLSAnLS1pbm5vZGJfaW5pdGlhbGl6ZWQnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", + "tags": [ + "crm", + "erp" + ], + "logo": "svgs/dolibarr.png", + "minversion": "0.0.0", + "port": "80" + }, "dozzle-with-auth": { "documentation": "https://dozzle.dev/?utm_source=coolify.io", "slogan": "Dozzle is a simple and lightweight web UI for Docker logs.", From 82a1478ca1508d8a7d81389098631341441ad4d1 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:59:57 +0100 Subject: [PATCH 46/84] fix: typo --- resources/views/livewire/settings/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/settings/index.blade.php b/resources/views/livewire/settings/index.blade.php index 6f9810215..9b53b9be6 100644 --- a/resources/views/livewire/settings/index.blade.php +++ b/resources/views/livewire/settings/index.blade.php @@ -154,7 +154,7 @@ Date: Fri, 13 Dec 2024 08:18:08 +0100 Subject: [PATCH 47/84] update stripe webhook --- app/Jobs/StripeProcessJob.php | 38 ++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/app/Jobs/StripeProcessJob.php b/app/Jobs/StripeProcessJob.php index 00c9b6d18..d61c738f4 100644 --- a/app/Jobs/StripeProcessJob.php +++ b/app/Jobs/StripeProcessJob.php @@ -173,8 +173,8 @@ class StripeProcessJob implements ShouldQueue $userId = data_get($data, 'metadata.user_id'); $customerId = data_get($data, 'customer'); $status = data_get($data, 'status'); - $subscriptionId = data_get($data, 'items.data.0.subscription'); - $planId = data_get($data, 'items.data.0.plan.id'); + $subscriptionId = data_get($data, 'items.data.0.subscription') ?? data_get($data, 'id'); + $planId = data_get($data, 'items.data.0.plan.id') ?? data_get($data, 'plan.id'); if (Str::contains($excludedPlans, $planId)) { send_internal_notification('Subscription excluded.'); break; @@ -218,9 +218,18 @@ class StripeProcessJob implements ShouldQueue 'stripe_cancel_at_period_end' => $cancelAtPeriodEnd, ]); if ($status === 'paused' || $status === 'incomplete_expired') { - $subscription->update([ - 'stripe_invoice_paid' => false, - ]); + if ($subscription->stripe_subscription_id === $subscriptionId) { + $subscription->update([ + 'stripe_invoice_paid' => false, + ]); + } + } + if ($status === 'active') { + if ($subscription->stripe_subscription_id === $subscriptionId) { + $subscription->update([ + 'stripe_invoice_paid' => true, + ]); + } } if ($feedback) { $reason = "Cancellation feedback for {$customerId}: '".$feedback."'"; @@ -228,13 +237,24 @@ class StripeProcessJob implements ShouldQueue $reason .= ' with comment: \''.$comment."'"; } } + break; case 'customer.subscription.deleted': - // End subscription $customerId = data_get($data, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); - $team = data_get($subscription, 'team'); - $team?->subscriptionEnded(); + $subscriptionId = data_get($data, 'id'); + $subscription = Subscription::where('stripe_customer_id', $customerId)->where('stripe_subscription_id', $subscriptionId)->first(); + if ($subscription) { + $team = data_get($subscription, 'team'); + if ($team) { + $team->subscriptionEnded(); + } else { + send_internal_notification('Subscription deleted but no team found in Coolify for customer: '.$customerId); + throw new \RuntimeException("No team found in Coolify for customer: {$customerId}"); + } + } else { + send_internal_notification('Subscription deleted but no subscription found in Coolify for customer: '.$customerId); + throw new \RuntimeException("No subscription found in Coolify for customer: {$customerId}"); + } break; default: throw new \RuntimeException("Unhandled event type: {$type}"); From 8a195c8cfb353cc9443f07ba9a93167198b8c4dd Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 Dec 2024 09:04:46 +0100 Subject: [PATCH 48/84] refactor: change OauthSetting creation to updateOrCreate for better handling of existing records --- database/seeders/OauthSettingSeeder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/seeders/OauthSettingSeeder.php b/database/seeders/OauthSettingSeeder.php index e004d1450..473b3a85a 100644 --- a/database/seeders/OauthSettingSeeder.php +++ b/database/seeders/OauthSettingSeeder.php @@ -58,7 +58,7 @@ class OauthSettingSeeder extends Seeder } } else { foreach ($providers as $provider) { - OauthSetting::create([ + OauthSetting::updateOrCreate([ 'provider' => $provider, ]); } From a80d17e097fe771f2345d455289476e18e16d66f Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 Dec 2024 09:13:23 +0100 Subject: [PATCH 49/84] fix: update OauthSettingSeeder to handle new provider definitions and ensure authentik is recreated if missing --- database/seeders/OauthSettingSeeder.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/database/seeders/OauthSettingSeeder.php b/database/seeders/OauthSettingSeeder.php index 473b3a85a..d486cf58d 100644 --- a/database/seeders/OauthSettingSeeder.php +++ b/database/seeders/OauthSettingSeeder.php @@ -22,9 +22,13 @@ class OauthSettingSeeder extends Seeder ]); $isOauthSeeded = OauthSetting::count() > 0; - $isOauthWithZeroId = OauthSetting::where('id', 0)->exists(); + + // We changed how providers are defined in the database, so we authentik does not exists, we need to recreate all of the auth providers + // Before authentik was a provider, providers started with 0 id + + $isOauthAuthentik = OauthSetting::where('provider', 'authentik')->exists(); if ($isOauthSeeded) { - if ($isOauthWithZeroId) { + if (! $isOauthAuthentik) { $allProviders = OauthSetting::all(); $notFoundProviders = $providers->diff($allProviders->pluck('provider')); From a53a1349e3cb10963437e07c5adc1d3fde5ad89e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 Dec 2024 09:31:35 +0100 Subject: [PATCH 50/84] fix: improve OauthSettingSeeder to correctly delete non-existent providers and ensure proper handling of provider definitions --- database/seeders/OauthSettingSeeder.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/database/seeders/OauthSettingSeeder.php b/database/seeders/OauthSettingSeeder.php index d486cf58d..bf902175f 100644 --- a/database/seeders/OauthSettingSeeder.php +++ b/database/seeders/OauthSettingSeeder.php @@ -32,8 +32,10 @@ class OauthSettingSeeder extends Seeder $allProviders = OauthSetting::all(); $notFoundProviders = $providers->diff($allProviders->pluck('provider')); - $allProviders->each(function ($provider) use ($providers) { + $allProviders->each(function ($provider) { $provider->delete(); + }); + $allProviders->each(function ($provider) use ($providers) { $providerName = $provider->provider; $foundProvider = $providers->first(function ($provider) use ($providerName) { From 3a76ffd6accb1c9f7c42afbebd801ff1d1e7fbc7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 Dec 2024 10:01:20 +0100 Subject: [PATCH 51/84] chore: update PHP configuration to set memory limit using environment variable --- docker/development/etc/php/conf.d/zzz-custom-php.ini | 1 + docker/production/etc/php/conf.d/zzz-custom-php.ini | 1 + 2 files changed, 2 insertions(+) diff --git a/docker/development/etc/php/conf.d/zzz-custom-php.ini b/docker/development/etc/php/conf.d/zzz-custom-php.ini index 33fb865e0..dcf048904 100644 --- a/docker/development/etc/php/conf.d/zzz-custom-php.ini +++ b/docker/development/etc/php/conf.d/zzz-custom-php.ini @@ -7,3 +7,4 @@ ignore_repeated_source = On upload_max_filesize = 256M post_max_size = 256M +memory_limit = ${PHP_MEMORY_LIMIT:-256M} diff --git a/docker/production/etc/php/conf.d/zzz-custom-php.ini b/docker/production/etc/php/conf.d/zzz-custom-php.ini index a2297ad0d..ee18b77e9 100644 --- a/docker/production/etc/php/conf.d/zzz-custom-php.ini +++ b/docker/production/etc/php/conf.d/zzz-custom-php.ini @@ -7,3 +7,4 @@ ignore_repeated_source = On upload_max_filesize = 256M post_max_size = 256M +memory_limit = ${PHP_MEMORY_LIMIT:-256M} From f1d0134e215448df3fc3c547ee3654c4804f6cdd Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 Dec 2024 10:02:22 +0100 Subject: [PATCH 52/84] update nightly install script --- other/nightly/install.sh | 121 +++++++++++++++++++++++++++++---------- 1 file changed, 91 insertions(+), 30 deletions(-) diff --git a/other/nightly/install.sh b/other/nightly/install.sh index 4a03a5c98..def105aa7 100755 --- a/other/nightly/install.sh +++ b/other/nightly/install.sh @@ -13,6 +13,50 @@ DOCKER_VERSION="27.0" # TODO: Ask for a user CURRENT_USER=$USER +if [ $EUID != 0 ]; then + echo "Please run this script as root or with sudo" + exit +fi + +echo -e "Welcome to Coolify Installer!" +echo -e "This script will install everything for you. Sit back and relax." +echo -e "Source code: https://github.com/coollabsio/coolify/blob/main/scripts/install.sh\n" + +TOTAL_SPACE=$(df -BG / | awk 'NR==2 {print $2}' | sed 's/G//') +AVAILABLE_SPACE=$(df -BG / | awk 'NR==2 {print $4}' | sed 's/G//') +REQUIRED_TOTAL_SPACE=30 +REQUIRED_AVAILABLE_SPACE=20 +WARNING_SPACE=false + +if [ "$TOTAL_SPACE" -lt "$REQUIRED_TOTAL_SPACE" ]; then + WARNING_SPACE=true + cat << EOF +WARNING: Insufficient total disk space! + +Total disk space: ${TOTAL_SPACE}GB +Required disk space: ${REQUIRED_TOTAL_SPACE}GB + +================== +EOF +fi + +if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_AVAILABLE_SPACE" ]; then + cat << EOF +WARNING: Insufficient available disk space! + +Available disk space: ${AVAILABLE_SPACE}GB +Required available space: ${REQUIRED_AVAILABLE_SPACE}GB + +================== +EOF +WARNING_SPACE=true +fi + +if [ "$WARNING_SPACE" = true ]; then + echo "Sleeping for 5 seconds." + sleep 5 +fi + mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,sentinel} mkdir -p /data/coolify/ssh/{keys,mux} mkdir -p /data/coolify/proxy/dynamic @@ -39,6 +83,11 @@ if [ "$OS_TYPE" = "manjaro" ] || [ "$OS_TYPE" = "manjaro-arm" ]; then OS_TYPE="arch" fi +# Check if the OS is Endeavour OS, if so, change it to arch +if [ "$OS_TYPE" = "endeavouros" ]; then + OS_TYPE="arch" +fi + # Check if the OS is Asahi Linux, if so, change it to fedora if [ "$OS_TYPE" = "fedora-asahi-remix" ]; then OS_TYPE="fedora" @@ -83,11 +132,6 @@ if [ -z "$LATEST_REALTIME_VERSION" ]; then fi -if [ $EUID != 0 ]; then - echo "Please run as root" - exit -fi - case "$OS_TYPE" in arch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed | almalinux | amzn | alpine) ;; *) @@ -103,21 +147,8 @@ if [ "$1" != "" ]; then LATEST_VERSION="${LATEST_VERSION#v}" fi -echo -e "\033[0;35m" -cat << "EOF" - _____ _ _ __ - / ____| | (_)/ _| - | | ___ ___ | |_| |_ _ _ - | | / _ \ / _ \| | | _| | | | - | |___| (_) | (_) | | | | | |_| | - \_____\___/ \___/|_|_|_| \__, | - __/ | - |___/ -EOF -echo -e "\033[0m" -echo -e "Welcome to Coolify Installer!" -echo -e "This script will install everything for you. Sit back and relax." -echo -e "Source code: https://github.com/coollabsio/coolify/blob/main/scripts/install.sh\n" + + echo -e "---------------------------------------------" echo "| Operating System | $OS_TYPE $OS_VERSION" echo "| Docker | $DOCKER_VERSION" @@ -125,24 +156,24 @@ echo "| Coolify | $LATEST_VERSION" echo "| Helper | $LATEST_HELPER_VERSION" echo "| Realtime | $LATEST_REALTIME_VERSION" echo -e "---------------------------------------------\n" -echo -e "1. Installing required packages (curl, wget, git, jq). " +echo -e "1. Installing required packages (curl, wget, git, jq, openssl). " case "$OS_TYPE" in arch) - pacman -Sy --noconfirm --needed curl wget git jq >/dev/null || true + pacman -Sy --noconfirm --needed curl wget git jq openssl >/dev/null || true ;; alpine) sed -i '/^#.*\/community/s/^#//' /etc/apk/repositories apk update >/dev/null - apk add curl wget git jq >/dev/null + apk add curl wget git jq openssl >/dev/null ;; ubuntu | debian | raspbian) apt-get update -y >/dev/null - apt-get install -y curl wget git jq >/dev/null + apt-get install -y curl wget git jq openssl >/dev/null ;; centos | fedora | rhel | ol | rocky | almalinux | amzn) if [ "$OS_TYPE" = "amzn" ]; then - dnf install -y wget git jq >/dev/null + dnf install -y wget git jq openssl >/dev/null else if ! command -v dnf >/dev/null; then yum install -y dnf >/dev/null @@ -150,12 +181,12 @@ centos | fedora | rhel | ol | rocky | almalinux | amzn) if ! command -v curl >/dev/null; then dnf install -y curl >/dev/null fi - dnf install -y wget git jq >/dev/null + dnf install -y wget git jq openssl >/dev/null fi ;; sles | opensuse-leap | opensuse-tumbleweed) zypper refresh >/dev/null - zypper install -y curl wget git jq >/dev/null + zypper install -y curl wget git jq openssl >/dev/null ;; *) echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now." @@ -300,6 +331,22 @@ if ! [ -x "$(command -v docker)" ]; then exit 1 fi ;; + "fedora") + if [ -x "$(command -v dnf5)" ]; then + # dnf5 is available + dnf config-manager addrepo --from-repofile=https://download.docker.com/linux/fedora/docker-ce.repo --overwrite >/dev/null 2>&1 + else + # dnf5 is not available, use dnf + dnf config-manager --add-repo=https://download.docker.com/linux/fedora/docker-ce.repo >/dev/null 2>&1 + fi + dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1 + if ! [ -x "$(command -v docker)" ]; then + echo " - Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue." + exit 1 + fi + systemctl start docker >/dev/null 2>&1 + systemctl enable docker >/dev/null 2>&1 + ;; *) if [ "$OS_TYPE" = "ubuntu" ] && [ "$OS_VERSION" = "24.10" ]; then echo "Docker automated installation is not supported on Ubuntu 24.10 (non-LTS release)." @@ -490,7 +537,21 @@ echo -e "\033[0;35m \____\___/|_| |_|\__, |_| \__,_|\__|\__,_|_|\__,_|\__|_|\___/|_| |_|___(_) |___/ \033[0m" -echo -e "\nYour instance is ready to use." -echo -e "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started.\n" -echo -e "WARNING: We recommend you to backup your /data/coolify/source/.env file to a safe location, outside of this server." +echo -e "\nYour instance is ready to use!\n" +echo -e "You can access Coolify through your Public IP: http://$(curl -4s https://ifconfig.io):8000" + +set +e +DEFAULT_PRIVATE_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p') +PRIVATE_IPS=$(hostname -I) +set -e + +if [ -n "$PRIVATE_IPS" ]; then + echo -e "\nIf your Public IP is not accessible, you can use the following Private IPs:\n" + for IP in $PRIVATE_IPS; do + if [ "$IP" != "$DEFAULT_PRIVATE_IP" ]; then + echo -e "http://$IP:8000" + fi + done +fi +echo -e "\nWARNING: It is highly recommended to backup your Environment variables file (/data/coolify/source/.env) to a safe location, outside of this server (e.g. into a Password Manager).\n" cp /data/coolify/source/.env /data/coolify/source/.env.backup From 830a4eebcc1dfd86b7686e80e3780405c2a984eb Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 Dec 2024 10:09:53 +0100 Subject: [PATCH 53/84] Update version numbers in nightly versions.json for coolify components --- other/nightly/versions.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/other/nightly/versions.json b/other/nightly/versions.json index 8b10875d0..2d1633ed6 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,16 +1,16 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.367" + "version": "4.0.0-beta.376" }, "nightly": { - "version": "4.0.0-beta.368" + "version": "4.0.0-beta.377" }, "helper": { - "version": "1.0.3" + "version": "1.0.4" }, "realtime": { - "version": "1.0.4" + "version": "1.0.5" }, "sentinel": { "version": "0.0.15" From 28de478801a35ebf26ce5073e2a3334386bee4db Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 Dec 2024 10:55:17 +0100 Subject: [PATCH 54/84] version++ --- config/constants.php | 2 +- other/nightly/versions.json | 4 ++-- versions.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/constants.php b/config/constants.php index a02d6616a..8c6c12da4 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,7 +2,7 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.377', + 'version' => '4.0.0-beta.378', 'self_hosted' => env('SELF_HOSTED', true), 'autoupdate' => env('AUTOUPDATE'), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), diff --git a/other/nightly/versions.json b/other/nightly/versions.json index 2d1633ed6..2d96743f4 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.376" + "version": "4.0.0-beta.378" }, "nightly": { - "version": "4.0.0-beta.377" + "version": "4.0.0-beta.379" }, "helper": { "version": "1.0.4" diff --git a/versions.json b/versions.json index b6f301ebc..2d96743f4 100644 --- a/versions.json +++ b/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.377" + "version": "4.0.0-beta.378" }, "nightly": { - "version": "4.0.0-beta.378" + "version": "4.0.0-beta.379" }, "helper": { "version": "1.0.4" From 5087906b24eb3d4ac1fdb7204c2163198dacf38e Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 13 Dec 2024 11:35:44 +0100 Subject: [PATCH 55/84] fix: encrypt resend API key in instance settings --- ...pt_resend_api_key_in_instance_settings.php | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 database/migrations/2024_12_13_103007_encrypt_resend_api_key_in_instance_settings.php diff --git a/database/migrations/2024_12_13_103007_encrypt_resend_api_key_in_instance_settings.php b/database/migrations/2024_12_13_103007_encrypt_resend_api_key_in_instance_settings.php new file mode 100644 index 000000000..33e18e25a --- /dev/null +++ b/database/migrations/2024_12_13_103007_encrypt_resend_api_key_in_instance_settings.php @@ -0,0 +1,56 @@ +exists()) { + Schema::table('instance_settings', function (Blueprint $table) { + $table->text('resend_api_key')->nullable()->change(); + }); + + $settings = DB::table('instance_settings')->get(); + foreach ($settings as $setting) { + try { + DB::table('instance_settings')->where('id', $setting->id)->update([ + 'resend_api_key' => $setting->resend_api_key ? Crypt::encryptString($setting->resend_api_key) : null, + ]); + } catch (Exception $e) { + \Log::error('Error encrypting resend_api_key: '.$e->getMessage()); + } + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('instance_settings', function (Blueprint $table) { + $table->text('resend_api_key')->nullable()->change(); + }); + + if (DB::table('instance_settings')->exists()) { + $settings = DB::table('instance_settings')->get(); + foreach ($settings as $setting) { + try { + DB::table('instance_settings')->where('id', $setting->id)->update([ + 'resend_api_key' => $setting->resend_api_key ? Crypt::decryptString($setting->resend_api_key) : null, + ]); + } catch (Exception $e) { + \Log::error('Error decrypting resend_api_key: '.$e->getMessage()); + } + } + } + } +}; From a16cbdf55762463455379292eaf97e2924ddc0a2 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 13 Dec 2024 11:42:01 +0100 Subject: [PATCH 56/84] fix: resend api key is already a text column --- ...007_encrypt_resend_api_key_in_instance_settings.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/database/migrations/2024_12_13_103007_encrypt_resend_api_key_in_instance_settings.php b/database/migrations/2024_12_13_103007_encrypt_resend_api_key_in_instance_settings.php index 33e18e25a..ab9b3416a 100644 --- a/database/migrations/2024_12_13_103007_encrypt_resend_api_key_in_instance_settings.php +++ b/database/migrations/2024_12_13_103007_encrypt_resend_api_key_in_instance_settings.php @@ -1,10 +1,8 @@ exists()) { - Schema::table('instance_settings', function (Blueprint $table) { - $table->text('resend_api_key')->nullable()->change(); - }); - $settings = DB::table('instance_settings')->get(); foreach ($settings as $setting) { try { @@ -36,10 +30,6 @@ return new class extends Migration */ public function down(): void { - Schema::table('instance_settings', function (Blueprint $table) { - $table->text('resend_api_key')->nullable()->change(); - }); - if (DB::table('instance_settings')->exists()) { $settings = DB::table('instance_settings')->get(); foreach ($settings as $setting) { From 4901b1227939ba0d1fe104c64c122241a4ef3bbe Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 Dec 2024 12:03:05 +0100 Subject: [PATCH 57/84] fix: service status indicator + oauth saving --- app/Console/Kernel.php | 2 +- .../Controllers/Api/ResourcesController.php | 6 +----- .../Controllers/Api/ServersController.php | 12 ++--------- .../Controllers/Api/ServicesController.php | 4 ++-- app/Livewire/Project/Service/Navbar.php | 2 +- app/Livewire/SettingsOauth.php | 20 ++++++++++++++----- app/Models/OauthSetting.php | 14 +++++++++++++ app/Models/Service.php | 8 ++++---- app/View/Components/Status/Services.php | 2 +- .../livewire/project/service/navbar.blade.php | 9 ++++----- .../views/livewire/settings-oauth.blade.php | 4 ++-- 11 files changed, 47 insertions(+), 36 deletions(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 19d22ae21..2ed3ee454 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -218,7 +218,7 @@ class Kernel extends ConsoleKernel } } if ($service) { - if (str($service->status())->contains('running') === false) { + if (str($service->status)->contains('running') === false) { continue; } } diff --git a/app/Http/Controllers/Api/ResourcesController.php b/app/Http/Controllers/Api/ResourcesController.php index 4180cef9a..ad12c83ab 100644 --- a/app/Http/Controllers/Api/ResourcesController.php +++ b/app/Http/Controllers/Api/ResourcesController.php @@ -53,11 +53,7 @@ class ResourcesController extends Controller $resources = $resources->flatten(); $resources = $resources->map(function ($resource) { $payload = $resource->toArray(); - if ($resource->getMorphClass() === \App\Models\Service::class) { - $payload['status'] = $resource->status(); - } else { - $payload['status'] = $resource->status; - } + $payload['status'] = $resource->status; $payload['type'] = $resource->type(); return $payload; diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index f37040bdd..b1deb5321 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -154,11 +154,7 @@ class ServersController extends Controller 'created_at' => $resource->created_at, 'updated_at' => $resource->updated_at, ]; - if ($resource->type() === 'service') { - $payload['status'] = $resource->status(); - } else { - $payload['status'] = $resource->status; - } + $payload['status'] = $resource->status; return $payload; }); @@ -237,11 +233,7 @@ class ServersController extends Controller 'created_at' => $resource->created_at, 'updated_at' => $resource->updated_at, ]; - if ($resource->type() === 'service') { - $payload['status'] = $resource->status(); - } else { - $payload['status'] = $resource->status; - } + $payload['status'] = $resource->status; return $payload; }); diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index ed9af8c90..bcaba7107 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -1072,7 +1072,7 @@ class ServicesController extends Controller if (! $service) { return response()->json(['message' => 'Service not found.'], 404); } - if (str($service->status())->contains('running')) { + if (str($service->status)->contains('running')) { return response()->json(['message' => 'Service is already running.'], 400); } StartService::dispatch($service); @@ -1150,7 +1150,7 @@ class ServicesController extends Controller if (! $service) { return response()->json(['message' => 'Service not found.'], 404); } - if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) { + if (str($service->status)->contains('stopped') || str($service->status)->contains('exited')) { return response()->json(['message' => 'Service is already stopped.'], 400); } StopService::dispatch($service); diff --git a/app/Livewire/Project/Service/Navbar.php b/app/Livewire/Project/Service/Navbar.php index ee43dc911..22fc1c0d6 100644 --- a/app/Livewire/Project/Service/Navbar.php +++ b/app/Livewire/Project/Service/Navbar.php @@ -27,7 +27,7 @@ class Navbar extends Component public function mount() { - if (str($this->service->status())->contains('running') && is_null($this->service->config_hash)) { + if (str($this->service->status)->contains('running') && is_null($this->service->config_hash)) { $this->service->isConfigurationChanged(true); $this->dispatch('configurationChanged'); } diff --git a/app/Livewire/SettingsOauth.php b/app/Livewire/SettingsOauth.php index d5f0be14a..857de1df8 100644 --- a/app/Livewire/SettingsOauth.php +++ b/app/Livewire/SettingsOauth.php @@ -35,16 +35,26 @@ class SettingsOauth extends Component }, []); } - private function updateOauthSettings() + private function updateOauthSettings(?string $provider = null) { - foreach (array_values($this->oauth_settings_map) as &$setting) { - $setting->save(); + if ($provider) { + $oauth = $this->oauth_settings_map[$provider]; + if (! $oauth->couldBeEnabled()) { + $oauth->update(['enabled' => false]); + throw new \Exception('OAuth settings are not complete for '.$oauth->provider.'.
    Please fill in all required fields.'); + } + $oauth->save(); + $this->dispatch('success', 'OAuth settings for '.$oauth->provider.' updated successfully!'); } } - public function instantSave() + public function instantSave(string $provider) { - $this->updateOauthSettings(); + try { + $this->updateOauthSettings($provider); + } catch (\Exception $e) { + return handleError($e, $this); + } } public function submit() diff --git a/app/Models/OauthSetting.php b/app/Models/OauthSetting.php index c17c318f1..3d82e89f2 100644 --- a/app/Models/OauthSetting.php +++ b/app/Models/OauthSetting.php @@ -11,6 +11,8 @@ class OauthSetting extends Model { use HasFactory; + protected $fillable = ['provider', 'client_id', 'client_secret', 'redirect_uri', 'tenant', 'base_url', 'enabled']; + protected function clientSecret(): Attribute { return Attribute::make( @@ -18,4 +20,16 @@ class OauthSetting extends Model set: fn (?string $value) => empty($value) ? null : Crypt::encryptString($value), ); } + + public function couldBeEnabled(): bool + { + switch ($this->provider) { + case 'azure': + return filled($this->client_id) && filled($this->client_secret) && filled($this->redirect_uri) && filled($this->tenant); + case 'authentik': + return filled($this->client_id) && filled($this->client_secret) && filled($this->redirect_uri) && filled($this->base_url); + default: + return filled($this->client_id) && filled($this->client_secret) && filled($this->redirect_uri); + } + } } diff --git a/app/Models/Service.php b/app/Models/Service.php index 117677d53..5a2690490 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -46,7 +46,7 @@ class Service extends BaseModel protected $guarded = []; - protected $appends = ['server_status']; + protected $appends = ['server_status', 'status']; protected static function booted() { @@ -105,12 +105,12 @@ class Service extends BaseModel public function isRunning() { - return (bool) str($this->status())->contains('running'); + return (bool) str($this->status)->contains('running'); } public function isExited() { - return (bool) str($this->status())->contains('exited'); + return (bool) str($this->status)->contains('exited'); } public function type() @@ -213,7 +213,7 @@ class Service extends BaseModel instant_remote_process(["docker network rm {$uuid}"], $server, false); } - public function status() + public function getStatusAttribute() { $applications = $this->applications; $databases = $this->databases; diff --git a/app/View/Components/Status/Services.php b/app/View/Components/Status/Services.php index 70db62172..291841854 100644 --- a/app/View/Components/Status/Services.php +++ b/app/View/Components/Status/Services.php @@ -17,7 +17,7 @@ class Services extends Component public string $complexStatus = 'exited', public bool $showRefreshButton = true ) { - $this->complexStatus = $service->status(); + $this->complexStatus = $service->status; } /** diff --git a/resources/views/livewire/project/service/navbar.blade.php b/resources/views/livewire/project/service/navbar.blade.php index 342c071d4..f268096f8 100644 --- a/resources/views/livewire/project/service/navbar.blade.php +++ b/resources/views/livewire/project/service/navbar.blade.php @@ -22,7 +22,7 @@ @if ($service->isDeployable)
    - @if (str($service->status())->contains('running')) + @if (str($service->status)->contains('running')) Advanced @@ -70,7 +70,7 @@ Stop - @elseif (str($service->status())->contains('degraded')) + @elseif (str($service->status)->contains('degraded'))
  • g%U{!)DLzjP(n{6SDQ|Uz|FPmj|cXKX{yjnB8 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

    @sl))W{$AQ$m zy-wn=5ALYPmb1)~21w_P#+z?*b|LY5^01pv(m)k-OB%?6wr?XTAul=1I8!DMi#;TCfBIFqmC?OxMhhd4 zVKe%t5w!lANzt#4Y;T#bD%?us&N+ytyJsOsjJ5X1@lO2>9*UE0rA|@u3Pn% zkevf>>J1Z5p||`fY1=f)b{TdUeAURdH?nA#?X%u@!QcCxE;iDgX>S^+Nu1$Z` zlXEDeM;7~YWe~S+M(|Qg7g6MKR-2A>Qj*6d!LNT;FUB;va>mjaL@isXt)~g|GqCX6LKPt|t690o*66UaPc6v+%&1gH?azo*vWF&;22=>ASR- zQ&?cuIt*D{K!fdo^EkXy2z~}f65@j<)CAzCmfAf_($){DHZL3T@?Ut@Q^$C4HdJs# zL52B!j*n$D2knxIZ&l;Qz06xX*G!w^(cf!l*f0!$%#!p`iqTw>{G<~Q&xm%) z?1pLMhGp}O0^ngJ*WcE(2b0n4vDb0GEdl54`nt=q9v$E?1p}Cm0GPqU=-CvBk`(Po z{E%CA@1ib{jp}lv@+jL8!fuuGy7KG3Dr`7=SmkEr>Wc7oHAjqfCYcA2k9ceI_D@pm z%L|Yz3b4h^Z^nj@#3~U-C<}Kk)YVv;=(0tde#Oi9NEk|f*=A#%n+Bztx&NdpR07`E z$S^B8^TTS$(H{|j-7-^wnTEI~q-#Oa9zd@!Cjy&joC1H4*e%>rLjxRm8U6MtO1y8% z>u%c$;qF})b*|G7cvV!UxQ zl#Zp@Xm?JjN6Z!D25{e~ekm2;5D>E@l}#Bgzjv7k9RG@;&N;MDtRMc?D})_yeUEvO zwcKuBAxFa4C#F**`)?_;=~_ns{Q5f=MZX66u3bco9@JlxLFBF;!G23?^GPIBYT9|N zTZ!1#+l`NpO6wS9z95+t+JTB`9;!$Ue_<9k@jNhwyqW&3j*G<5qd6l%Fkb&u$4FHt^=G~SxYAM@Z9?<^|-F&~sP;n-Rl0kn!t z=fC9^?K66j#vhX@*tc~`g=?b(RX>2_l>nw6t?tsf;0+qKZWdn$iqK%cT@r>}w|yc2 zQ*@c@2PA;vC_1o(mEC~G%t(>@;HKB}J%Zo2y>0H)Bv6$5ceQ!oQpBxqB>46bVr39J ztGj|CZaFglq4QwAsKubTZnnoVyN%eop*A+$kV57m_S$In&89D|7RikqmV7veNm z>Bum~EZ*fabrEL0`Tg6*quA;ax0Jy4Qwzx-tQ${zY1AaV8aX8BfgD;*M;fKjT^`py zYdF&t>Cl+Q%}{9A>NoNw3KV_qyqfG%VQ-R`S~8?Jg8OP6Kly=myPh~Y7R`Z^lV;@= z)i*SghEo;l;b18fn=g#$rNfeMTlH8LCGj}AURG-X8$d5g_};%@!7Mn)bQ*j_82=V{ zOJF|Y@D#O%G;$FCUUgLTCVWpOG{E;LixCa~F;BKvef-%&o}Tj>#G-|)D68Y+ZU+u#5fww^t`FEBdXb*3uUT3XuU9~ zU7qEYT|GH~c5<3P{XAC&jBpOm+jC?&NDUE9Ar8t6G1OfXCP9RlwW&F(ZN3eZ=@4X!8TpefT@R(@`E z|3$H7Iuz(~RbJ$C6qVyNy6Cr(f}$TgJd#-qUyOBIg~qrP#1Dn1HFL$6-DrjAR^<+l zfxDbWEeKv@}J?!aY* zbIH?m4pYRiN8WMGD3Ah*U_qQCG(!eFlVkU~^>PpWVWp1UGF?NKb^8O835udv01bQK zfYyMfn?JMO%U^`Ox6Aog+6qz8evE-={RaWWqH}>nWet~vhQ9NUcRH!Z(9t zSH^@Av#l1y4+kX}&IV32_arGe>(#sO4eEa@$%O1YheS&+1dn_H!9{U1u5umfd}Qrd zUhlppCiLUnQLUd@Ro@&n!LWf8*!@k<(pyg0V)Di<^MS(H?jCo z(7EyzSrXs|`|sm!>I`X}MQ9Mg7CL~n_B3gr!U(x$3=lK6GXfu*Pl0C(x7I|!IUx2J zB1fLnIdxn1j16oD7^KF1L)HZ~@X}++@_M=&Y048D3#Pt7^GFvj$(Ac0Ntw$(eoYsB zZ<|j4ypg(`i>kYW7dmeQ5SeZ5T`Z|j@^<+^>*(*wvp>uGpT?}nM_9g%Pjy$nZn>f4nClV@)yBovh{!H|alKQ)4gbep#+IR-^ z9%d}JQ>$|&280ytaWVT%WUMTW64~24n;RbLhi|_T%lV?w3)QeF`3?c4x~TN&omgGD9o^w4VqXuV%?2mqkxQz7Numsy zZgtiux0jxUMpaMF#FNFt0m>%iIo4c-tMJ>bKtY}aLu%+`+i6txBULo75Y z;-8LL7SSFzZyQoaJ#5Gg5-C6k(dzW??cMA8ktY#WeLKcU~w2fHE$^^^64&{qqE^UPlW=Y4^CoFduwLD)Sy4FWUE zozDz-U;G;9s-7_W+7YAqg@DVWHFS*2V{C3gO38u+$oB+smU*Y}U zTPz7EseAzp14ib_(Sr*(8TYeeE|RQn=p$%uN@dnkntN-JW1j*;Yhbhq{0X<*58jw+ zTb$BqYV0cnrih06msR$PK6{L0tj~UHF|W&zXEH?TiU4F^MCtf&7ha z&Dek_y$x`Pww`O~p4|h>kE$T$P4as$+c#nDxYQOEYPT8s-I`Y6_LGP9*#bJ zC-MwSiJ>_jeo4djcAMy_AIXkV?oJi?CBq1aZ-@UVmJE@8G$=u-5FUM;L z!}0{Y&2fTXVyO2H&A$!=uN+Gx$EKm(ep?+8yOx@By%_ag-y1r-6rI%uh+|olTp58B zq%19j^<(FQ4-sETE0t`mx*^Zb@o7H{&s{Eqs}bldb)Tu_h6!Uf~Li~CP+e-?5D zKdvwk6R}MXT;U6lie>hVmn)K{R5rkvB1;r!UlWY20h?=;In&Pux%k&ghN#L`FW>Ul zu2jdhbhzpG^(bw{+zQz#)qW7v_elbH&`(2|JnJwyO8UYf~LCs;K}pm z%5FBn$e7IQ=i@5AANA28j0|hpv)R%EDKgJoGP6}b01nHKSp(k1*}i{|f#=t*DSZE` zW{__;o>^|cwcGpwj!m510pcOru388%+t`6T>}NWf+hJM`Yh>z)Rw4!J1S=5WxJW3V z@q-D{3a1E+u26Zx0fCVfz+ahBOOw1$;BV#ik`2~fK+%IBUF>-oR|8wT3UOp731JTj zZr8{>!>f??caV8vlE`i>Hu|W&_3JAXAB%^slxEch*)GoQ6^ZI6v7u9zGK%Ss?!GO2 zgf@!gk5o|m8hWbVIomWoWf@>=LeoXJjA)m9bkEM`90Z zYL^@L8jtp3jgW7z)%=?sBwKEL=wz<9to^TUDeZ60IsI?Or2 zXlQj-V5sN>G!)uYOEc7hB4PA7VwkJGI;e9Qe^s|TaqTjwQ){-bG?}|~x|p}WRw>XY zOBLJjI$9bIxz9P)ww8?5Y=!&sj|6#=X!h5+TJ=ajO}69!fwNro5&Mtu+ZiOvU3+;k zsy%SHPXPAhM*0)GFhTHF#G+;%3wnajBJ}`)fF9O`BZN1~y9 zb2FBii$zX~2I$lQei$@Vds~6*WdE7|Wd!SC3 zOCYbgi?96X&P*Nj6=$1-Kjt0Yx?O*ns^Ge)Nl3w(vZc+k1LjG_k09;b70 zmp^$otzGr29s~^eo9k)lWEaC2{8*<(X_O6!ZHUkq+NdQY?r&~>$ZnR7uBj22k?h#q z{T(}=BOiNtZpx*koJ6el5%p#(e>h3eIXPR1UROFw>wQeRZ2KDsf3J_?vj%wO!`vwn z{D(DOxGY^Ygg&1))`gGX(D0Wxzs^i5cXCKtKMb0xC?u2J+ixY31ZkL9={H9$4pc2D zpT8xz5s2Q;lAAb9zdxwiZ?Ba-=+u>U?AchnjaF~Df-nV~6D-UkF&9uaWDDNV;5S+a z2Tsl5h%z6%p_VGFViWIsaW|Jvb$5LmKUUz@E*lE!%Qe?ot*RGNABvO8TdhRrV`b-@ zCHd<2Jq?}5OT~87Rp5sgldEH3q8}}E2i)D@X+lA6ai)TpEWk}eUzRL!3!-xqC$SLm z={qe2K8gui|;);q*+vR&0z-Em#cUjS@XCHKx`^+4d#ovbV=#%w^5VpFj5LGMHvB%hl5Xz9np5Imuyh7O0(E_K)x+SyxqsE~oQN@X}9}+QDOjG7y zBRzgO<%b0NfT@6AHq%B3s*aYrd!k+Yk77hUV_vBmy>)p<$tAS4!Y{Kcs@$nvISUn? zRTByi@S>MI?}S(K#%46;O;tY($&hS>TPRa`?&6ql?DOq9?sV^;=UC_(5KCUT7qDbu zkDoNoG>qAuyW_AZ8CZ)cHA;9r`@pP7;V>__yPr7x6wlGqlRcv7WZ-;|tHwj|hia1z z(dvds%54js0hm!0&Kn_Jk~}$OBK-MG)LUNr1sr=YY^&Xh8eA+D^EMuK#m&Mm8%vR= zX}nX&aU#9DF0b$3?o3A~z3)c(bzBgRt5is2rIU6WLbIi1>tV_4A_qRV_my7DHp!fG&jUmFDWRe3nO3^jR<&N!ntHO|IeT(560K@bmhP;y z&@8u-Cb92~`zPnP#oSk4dQnY_U-#6aWm*XHIL3-3Z}V5}blOY|^<#1)w{W>p6z5x9 z6_NbA5RieMMr1PoOUZ_VJtKBtk#~x~;X|O2n+r1^y$@(bPf*|!Gy3K}`1CAV$YsK+ z*0hU^e`VYwO{+xzf*btZBu>q>j7}}rf(v@rAAS9=gJblrk2soc+c>=~=CMkAywHsU zF36g0_Bon9hq=4mUi)QPGY>VNE^suRE);Sb`*__q_VbdSD7h62^+%=pq7u@TP($VM z!A`*!B1`KYJ4eUY?@DGrBuxfHCQUlLIQr?AcJ#9`YkAyDR$W16F}=-hM+BV9ExKHK zmUeg*p0zX%e&^@^>Fc@Usr9%>I(=zARUSR@5h0Rn)Pbm1R1JW$nK-joF+3YyM!vaFBiT%DF+98HBH9W9$j%z>B~t7ANY<(MTx+6p z_0aV@5Ni=?!b~XQZ851mn>KabY3|($oZ+fH_icTP&uiFyQoO-CttLQMOSc#u?&`LN zQM=oNaXP!UcX*;rHQQ1l+7xQz{h|TO#%Q}~g$fxkJk_>SnBVMBSRX3ZP)pQ%I%_4o zHZ z85ONrzRkg0cVeL9G3He{vvA`O<@&o+`o;`392ionHTg zuO*6N4jefN3x+v9c(fk!(#DUuKGV9f7Z>wNnH#gnGFm9zV_*al5H!W$0S4A)i{ep51D`|74Ad0y)m4=|zRQnrDg zN0P}N*ZC$+978e>MEx_FQd+v1&JzPY&zY_Dj^skLKTV$`H*I68&kmhv7>=GP&|7=K zcw)t}>{g%(uu-T2G8X**vX=sbb^V6ef9hsZp=Rr@izwHw80-GY`iAj(8vuYv8i6PbMmg5E@-no(~X=t7NTl& zYNCilz8#}%QmYV!Gg)f|blT_zh_TcW$4!)8_o>~YJe@wSvFj*vnMu#JX$#O`ZF7s8 z@^fI-4JkRdrRk}hb7!t!dcs`0`K(I}C7b9xIUnz_o?`obG%nq#zh$$x4oFt%39#57 zwe1lm;K>i=s58F>j)(}1_S|8wGQgvCmv7lBP+^}GAb;(|HJ$YaGFD0y|A9Mg^^8ZRL6O#qm4uv5CWO?6F^ta9^U359ra0l<*M2{_;`aWs0<~TI@HA=N~;W zjV$P>4YEIH+r^i z*ljO2v}G^q__`5~!`+A{4du@EJCh!7?D&=947pF{`Tg(Nq0d7%|-X8uJ+W?_wp!Q79Br)h@$Zrk-5 zPa6d^vfA!!*G73xEEDVabFtQ{+bz6WAvD!>teaU4wP{CoJ+l6^95?==1&c`6Q!*rg zST_>=QGrTV!{(Fj@SVce@tuOEM2AAvcDIF?5;g^n6&!{UpU4Gt;tcb1smqDwm%?{g zRncn?zV7tdey+0Z71iFG%d!xm;OP!!7byZ%&OR7~*igz_#``~9S7GmW;*`%y3EwK? zG7!zamO)#oq^uP=gH0#5|AfSfhg~z5>*bYsmj?7NB%O7oQqAz6d76ClI0RoSlUeVg8TCvxeL*Dasx0Noq#SgubE)lM{I`EeG zpwCh8J!X%%6S9lDXQ_F&RVseSmFzula>(*y2c-dfvhOBc3Q7Z<_yY+GuxChBIovKa z7pD)rEQ(?_?wz+2m-OKT;~yH8M961p(byd)W9WmlB`isw=#5YaU(nA$GXK-{_LZ>Z7bZ^W`Sb^W(i4(_=`=W=K zhwt1_E~R@pBhvA3vI?7FA)q5!g)8KEHmQU?&6>7#xJTUB=?dd}zGjYmto>|=rowi! zeLDU^ho02vJB*tyewZ1pHA+wBWdi_C^PJ~^R|oTXxbuf5*Z{cEVB;iVnj6%!@R?fc z@Pi7>g+_mxGzyIYk*R6=Wd*JveU&EzH zuAD4p1U&5KrN~$vBw5R9{21{p?4v@n<_Te#gPfFB3wMl$TC=?;#cK2feTrfzM?^M> zRZ57`jWQCo`5y6Dd8W0N_ATo$GPaeR;SVG#YC#{>21zsoL|R5ZKMTvwYWY|tXUrlU z?QiX`q{c(S<1Hv1krFG-BdtDYVDy@+B`e20tiaL0F`$M@he2B4yW+Eyz^7wIVR`X> zhHVOM@mPZ-T=ytH7^=<_#t6!*>a&%nJb!w^9iysh`1MdQELkzqkgn2hbxZH2P-AHS zEIE5l4&=Qo8#YD#EnKi);Z=Ss2oxl!8FdsMAY}o0H!pC0)=a?Rx;pfYOmb_3fNGpZlw^RoyM=Ba16-j{Gjd$8cycCg5&HXy5G^% zY_bE=Jjrm3RTPbq1Fu`~$mR_wJx%s|Rop?(LM;D@zLPM_PSr3^M^)$<`ximnme_3i z;vs~YQM`^X*5tf0wtKUUvKR`3CJ-uj7E$WstSz6!?|&bw0C#*85>14>5OJ>bj} zKjrOb z>o`LleF=2wMXQ$xW0C;fBO?-K6@${pGB4{eS|Xkh3MJAzS1PS;rrysogEzhei%jJ0 zDdChD$(M?hR+N(vFkFPumDCZ|NY10Tc{r&7K=ns?lCPiO)&Y6fQ-z!v$oQneGrH&Klw`6Fw$v?@oNK>~2$HxmTk$yztX>FPn9K7rX*Rz%{d5pSlwlc>_(V5SQr-N52RJ7HNy&h8})FLoEWjY=x#Th-k*zm zEPd}NxKGigGX>28T%_pnz3d_w&0UsfFXKnxH2&~sk` zFueYVn&(Q*!y?+E!&Ekh+++x+n0WR?WItF$KSBm$c4<;w|Ei8(6XVLrM>d`3Kz!F}IXzEgl#Yb-H!qCJmw|l~D!|H3m!Mj*rHzpFZ(rD~wKUU)pTa zoDO(6?a$$RE4cZJ7n%SSc7QWJwiP9zrbv4QFZI;-d&zf$$=yu&q~C2)PmNzrGRAO> zeZ#Q3Kzi1PU6+|jth~#&C*f(?rIUU8qoeUc*ctTk+#)HwQQ;DQRxsqm_$s6cXh`Ax zc&jk)4L8{*l?eSK?N|{kGXvX1;MjoU(0d$J8=z#|+{&;28%}gSy3{^VjB086$So?qTw8Jd zb*X9os+wg5 zcGn(_Z&I9ANYhx9!-CX4TdqaRm9Cef>0MP?T)NZTse^Ur>ShMwo1cV*1sTgTYq;p{ zw!DKbgYO0hmoKXTmN$3 zI{%(~2Q{)5 z@Md0nAUy1kW7l7;HJm8{U;)^9gY8@jY5@(3X1VH({)A_Kjt8;pWA33>(2ZH=BBNz0 zZlj2?fe?y(`us$&${Y(YiibC@`WMKBcv8vUd`zM>>UTRq{{~8slv8m*_7X(-1-|Zd zir&|aaHGZ)c~lDutdFh>0L}u?{Bf^f=$>xK08#RsmzO9o?o3H0_r`5ZFeP8Qe#!N~ z4^C6D2&JR(!zC|%~9fa7Q}8FOf9Od@0EI1-yI~J zKt*%Cz3qt8TP=|K4r?qr1Bu)hrxBfK3DGzJk=km$zK)VZs;^6Rs&5YGC49?xSuSNsrsZRx4=rOS)nT~Hu*d}i;!Nkd&sz8W?CG{! z1vzH;-STAWpF_Ujo?OKQzD=M=Nwj#l2CpEDER>tka7+rxu2VzA&7}zBwox$;p>2a1 zusoGCrnngoTd5OYq5~-LzQ6Gl1mRmO)~Mn$RmfFC$Ot?j+=QY7Zu5IyOB9#yNJ5T0 zUEdHikZUK*#M?Q27cjW*R(D&We)x7X@J>ctA(9cxj0;QL)Mj1nv;-6I05-odjt#5- zsEPHIyaD9hEC#5rq1ARJHA8pJTv8Pkr9yOI&xm8(Yr^(ot{7N#oq`jCXa9;U7q3n+?j=XV+YXxd4S;U&UAmrd zhnEg}m2;pPc|Q#-XyD~f?CUJ@$0nA$!Px*Oy5Bysj>gd1S{0>FyOJN8VxRZGSL_g= z79dUKrGk&=uMOgk-M*B{)%gzom|Zi$ptKBOWFuwQfC@>s90vP`wCHkOHA}{$BNQ-9nqa59)%( zs}3^zhU$j>9+6_-Uaa;^)A0jvk2I527Vj~f4&G#<+!OkI&^{~^BY=4s=SzdkN^C(~ zvQ|#pBebAE9Dj}nmtn)Tj1N);hT;(^zS!-S=DS#3bK`~m18WVabrcjA!oKIv;gq~f zY5(ekoo`M7w&ud{b)%UqyhW1 zEI3hDU=AYKok?IZ6QvX{zO-O1gDiy}VQ%oqbz)chMrIMLcS{vKne8P!2G8?Jo7Y%K z+jo{bGar7uLtLA)Qrz-df?P7^hQ2_kW3zli5iMpPhW#zpl7yNlo7|0iH#U{qKLp8! zx0i6=>isf)#f>#8kcx&Gm&CyCmcpbAtw!|2p_`OTjP5VvlXTs2<`OdL9n!@ETQ!ZQ zcpLAy%(V4BPbE(az|aCE^J98;Ohv`X7ZMKQS1#po>eXnmY{i~x zlDADfmk5Fx-dry$aa1x5U5EG1EUY#@8{77kIX7Xe0F&JAhe_Coh{l z=|1e8UK}HE;M}!eD_=(|ka*|eHqUmweSNg`aUGbrkQ&kR4|^FKSvw2fPd{iD1jv7~5u9=i?oz{ea z8gaQjokgOfPk~e;|C{A{NCqR6M0fDHVH=;}?x6|T6PQ++j`gFlZC{#X4YFj}-Cj~= z!amLmZ5uH)VVCgA7ocH|+7wUN6YxS~u$g%(5;d?ihmyz3!NoSpdNJsKOEHBANA(I_|$7&E{4(vTl(=^|8y)aMa^X@RUnlGYb0J`B6IBqK&bgP$_9_RuvSUr z^Wry$$L&mUZ3~3xL~DD?ORM>P8a>a`p2bSf%PldPfT2gJZALK-o%qCLb7Qo0%6nH& z=~j|aVWej$oB!MnDH8|I_G0#qVXH8~Hyr|S&0@2to= zm3-Z?cT)Fj33Y*}WKFLH6%y60DR{a)TS%uDL&#^ZXFb?nK537|TxL$Xs0LO@zLD20 zzUjYA-8)&Tu{~JzfrIXxmf%0<$js+H7@N6ic)+;=plfMFX(m$!wZF$~_<(}t$(D_c zy34i4H|#$$uDte8y{t%Y@lu5w+AVV7%pkUY)hNy1DvFyv9AS!=^_yD z^UN!GIL#VeU3MqL+scnG$qK`f*UeI?@HGN<|IKconM7K~jp`86?@9(X_olBBp-u>Z zU1GFdRCOKku4K3GbDV1L=KA|=aIb*eFX8nHm#Gg=T8s{iUooE6nM+k9k`MGmK08v! z0Kh&m_-NXCYYrkkHd2gV}&+{GH zycCrT_+0|-I;W&-4rzgBt7Ud=#j2|A0DIG0Z{l+FHcjnKAqyt&1aVQ()^B0gV#RZ^ zI!m~7)@z=v=F1^o->)8c^3J1B%;jO&$L^ax^7|FI+hA36a`%LP8N`h<^8ORWI{|e> ztlbYXcxW82zYBWkn}Jo$$nfnfhZw;@#22WogN;QpXF8XgG7DzOPRrFVnL6uTe76h7 zBd-~MA!roJdI%iQ0T0qEDw>e-X;Dwbv<}H{mca7j(ftkIAHS`2KIEtJcF}=KKj?)F zMoUXd@T{5QuwY)l^iBnawVQt6iF>jD3-RRnnoWhVYLFVSe2&&|-qvG^BNVlxdHYiP zNsj#@l}w0VBrGJSJ+^8-4tPACYLmny|0rRv{eA_gR%o%$G(eQ~%SG7+oC zZAy)9mqzH$X+1TqQHK?nFoC5b;s9;OSsUsy9{GCb$7_*k3de)4&DQdpV(Moe7Gl}7o?*q_UL*C?I`guOCmuY%o*eVMk zCF?J_V$Es3YHPYB?KDdK%4Oq#_{L@?O^j64Y=Pla+)B9&?4AUJy*yKebsAy> zi)&7q!4NcnXI+G4XU+g+wNAT2Zvjk5_Jl&bJO0N;(buuV2xuU(b#&Hx{-Id+@OsTvq1IWW^3)u zpVIf7DnwSKrhppR>X&_9d+Y7Cn+<$=>qeAIzh3{*F9QO5j^Y8|@tgo#P8Uvm$A5qQ&(*(B)msvwH~`eF zA|}ul&7=DO*tSZ_q^M^kp8 zZb4N5=jJ2R#SqQTKWf>=2MTuxq>v{E#QrS!Glp{^$Z4-Z&;SN91@b>^#s-wbwn5M= z1`_Sh7U<7#4!%QTcS`(+8kc7NXcHCS2!JDlpdAb(@1K#KU<0&xLWz8U8*uy2Ld9S_ zAI1~9&I>U8*>`XW0ag4TFc<*Gx*6r_#o@Cq6cq`aJ{x&i3~`PBqvhyt00^$t2j760 z&#y54OxCLw6fJ-<9(h>~aSi!1GETsZ)cKAz(E8^H89jqy0#Mwm4<-QN=07Mq!Wk3j zB5}TB1B6uF7T=em`Kz?*H?L<)XhBH>4V2FBYyfp-?qthfum2V1kY-MRcz02T0n`=; zzHZ6K|5sPl@6jK=9>;b*b<>&K3Mg3gZH0zk5M?5G!P&_;LT`D221sA*S|Hs8B!PQe zXZ9^9hvtY@T*P%sGvU4qNwNjRZz8e`WI)?%fF5#;3Hkn0Rq(4G#a))5aJA-$1zd4B zCK*2W1>v3l6$Q`-L5c-Ybd$sNA+9uILjHfkg$Ca#*7aHZgaN~Wli>4GTK;;sztWfO zI{^Uc0H9>bEjAKIf$^7XNq_g|{FRV@VkVXR7=S$~w^&d72Am4Pb%jSs2@qT>Tlmym{(mck=H-RwW=LfX0jh{4f< z5(jrEYyoVqC4PMov-P0YHlS@z73&%;O&Eo}BTvNBiGyXgC`!;t#fHB~dc8q7wpaSB`J|+}-5p9m* ze1`x6Wc9cR4Af$P^CoPzQTo3YBdO4;bpXCttSdt`L=IoD+_?YGcF}Kon3xPj;Q`Gh zx{_4;p>Mq{tVzhz97$t2GL61k^>ESkXQei(acfdhrdAoBx9& zfbBy7m})>1S)j3o7733D#a+zlFae-yTB9y{8&oPT!sHK1r5?irDw)Qdwy)Je=%H7; z_akU8@GM0C3IJz=oLZDy%+Yv5Z6B7q@LuG~5%QS8eTZg>E;-c=&?BISGUYCU7Xpf6 z0*P^J)rVQ20XG2NpZ6m!@@Pc=EcmKh;83mHVkW~Hs@QP^6#D>wKfL`SKpB51P>;Up zV_HKb`iu`CHqu7BNM$#{HSG(a$fyQGG>3259fGW~>=eI17rr#Z5Wv)^=6wEr02pvK zb;|?(E-(GnEf6mk0XUtO=VBiQ0*5-risSDhfnOtq?HvDKP93}UHcq<`SKOj}{$gCy z0Gt<~C7KebhE$4;lfF+BYvmCW;4WUO}RsLcHUx09xn^6K(2?;O6R~ zfcK@%l+gm?Dheb9Ouqc??F+0)+*$}or9gaSuE*cOMHr99247^sRm%YUGW_9afo>IU zKL$*yJo7?a>jFS(K+D0kQph14&}*vAd?8^GP=K||x$g7YRWRBJ!4HaANG_5`@df~& zXReP#;v#&2L(Z}EYkzN8{ru45NS)h)8UVbL*2rFu-I@dl(GG(#$rUPpvBw`@d^36> zKp}8g2qJui3&%o~TZYP9WNiAShdx&j4JI|T^ZQq90BOibhkrPo&aY+!Mq$KDkh}xP zDS7Bdh9i_6Ph7iP>dpo7MSu@me8A!3atlRktw~@6;Cfok^3O8u&xzEj=UMpL2I0xWtq^)z2URMv4eNa8$;>NtK z09n9ty0Mjj^}iPJ)CUy6CNCV-_$6)(Vg;IceQ$aDbE{2Mh0CH?!=wXcTbWbo;LYF& z&e(UKIIo|Ge$w=&j^)1IS^D(KN3QnE6!%CwaQyBM*4uEDz4Ox?P`+oGuRi!HH7qCn zTTZ#mN~xDz!B!P|+OqkEgXq*reM;fn8NTOsrRYZQ$+6zlO0|?lADIy~l(bzOgEPt( z#|XI{h+;GF$gruBZ4BQ!V=d<$$NBj>H8j^0NI~$Bdv#V{!kp zJ{96c20~z*OvhhMWDOFGHgaO8Z&JBSFfZ#!~Ki)e^KPaU`KL7>*Gt>?=QKuy(wXPp2PDW z2LA@)ErBGyAm=%b&DOd)^L&)}{@^cCpoS*&0}-v~;q}IMi(#KIP4r3ri%KGBr^oPS z)98*KgAbQ0gf+=4P4zcX6urbndJ4bmFUfPXz3k0*;+Oq9D>P&X^UA%|(SjHz!czkI zH(t~KjROZsz+co~V)^L+t=4cKU9}A3Uv2$h29ZOKkiQvGXVPn*sk4lX^*aTLAALDp5OXPAyEYf(Bi z?$~avh8F#F;XX;v|D9`M zOq9dsVPL(o^R3Fe^VjdU#^=}V+xn!!H$b_>=l&*_Lm_|EGXJ}@ z!WgH^)|KOrEt;*GyZ`aHzd2KH0i0(ZSlVnd87KLB%#M|??f$AF#1Joh);9MCA%;cx z$-X6~`n$hzoNo+>wKS6*%Z~atiXp=*jrM63e>H@Waf~7;Tgy2p5;<(^Ahxc_tG}r* zVE_<)?oWO!tMw8I;Wyu3_#J~PYNw%dRqI2{_JJ7r;e%7xeG{eMZW51SdwYGVPwg}K zI@NGu^mF_Dh2KF)Fg7J`mY){!d%S1JTzzT_=skBZv7!Bqp+Ro=z*gye(5JuxWnR}J zilAAI627?K5Ce;m5 zS@F1{^%vtBIIypJH>7wIs@BlV}{%gKs-VS)I0%=7B4{Xc&dnDG%!B2)(-9=Oa29iD@{t%H_?F zsndUe&Ww5__&yGE=+NTyr{EPQw2LB=nY}jBV3!e7Y~b&;MP2K5w4t2fg9-l$VZJqh z5}HK%G*F+W6RceGylb1cre-KiRK;?NDZ5+m{d z9k!^$d)@~VulZg{?__37v2r8Jmd&B2{*Cyl(x0`w`UT;$?ut1yaIB*b+UGGHMqI-c z;VV?DSiX4F6ypD88w4XMqIXlwm3AHOCAb-<{8>a z`Ib~7PtD7pbA+Y{^pGc_jePU|xSfDPkLWqtsI9fF za@#cbbQ<5%tLO3{V=v`@W@|ML6ZW!A=-1rv$BaPLinx2?8XT=O`5;>k6;6ruIFglJS@gu- z?u~60Y?)2iLuVxUF{s+;zzL~K6ZUcEof)Gk@aU~1yNsEjS(HmBZm513-%4`0gLIP6 z_lk@j^1T|R#mH*d*MJP?u;a6=P-$J^@Aw(c5!FUtGc#hFKO2|eKmMC=SQ$> Date: Wed, 11 Dec 2024 15:54:11 +0100 Subject: [PATCH 16/84] feat(notification): add Pushover --- app/Console/Commands/NotifyDemo.php | 3 +- app/Jobs/SendMessageToPushoverJob.php | 50 +++++ app/Livewire/Notifications/Pushover.php | 176 ++++++++++++++++++ app/Models/PushoverNotificationSettings.php | 61 ++++++ app/Models/Team.php | 20 +- .../Application/DeploymentFailed.php | 26 +++ .../Application/DeploymentSuccess.php | 37 ++++ .../Application/StatusChanged.php | 18 ++ .../Channels/PushoverChannel.php | 21 +++ app/Notifications/Channels/SendsPushover.php | 8 + .../Container/ContainerRestarted.php | 19 ++ .../Container/ContainerStopped.php | 20 ++ app/Notifications/Database/BackupFailed.php | 10 + app/Notifications/Database/BackupSuccess.php | 12 ++ app/Notifications/Dto/PushoverMessage.php | 50 +++++ .../Internal/GeneralNotification.php | 10 + .../ScheduledTask/TaskFailed.php | 25 +++ .../ScheduledTask/TaskSuccess.php | 20 ++ .../Server/DockerCleanupFailed.php | 10 + .../Server/DockerCleanupSuccess.php | 10 + app/Notifications/Server/ForceDisabled.php | 10 + app/Notifications/Server/ForceEnabled.php | 10 + app/Notifications/Server/HighDiskUsage.php | 14 ++ app/Notifications/Server/Reachable.php | 10 + app/Notifications/Server/Unreachable.php | 10 + app/Notifications/Test.php | 17 ++ app/Traits/HasNotificationSettings.php | 3 + ...e_pushover_notification_settings_table.php | 47 +++++ .../components/notification/navbar.blade.php | 6 +- .../livewire/notifications/pushover.blade.php | 84 +++++++++ routes/web.php | 2 + 31 files changed, 815 insertions(+), 4 deletions(-) create mode 100644 app/Jobs/SendMessageToPushoverJob.php create mode 100644 app/Livewire/Notifications/Pushover.php create mode 100644 app/Models/PushoverNotificationSettings.php create mode 100644 app/Notifications/Channels/PushoverChannel.php create mode 100644 app/Notifications/Channels/SendsPushover.php create mode 100644 app/Notifications/Dto/PushoverMessage.php create mode 100644 database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php create mode 100644 resources/views/livewire/notifications/pushover.blade.php diff --git a/app/Console/Commands/NotifyDemo.php b/app/Console/Commands/NotifyDemo.php index f0131b7b2..b6e5015f8 100644 --- a/app/Console/Commands/NotifyDemo.php +++ b/app/Console/Commands/NotifyDemo.php @@ -62,6 +62,7 @@ class NotifyDemo extends Command