diff --git a/.env.example b/.env.example index 292afb1..37b8f8d 100644 --- a/.env.example +++ b/.env.example @@ -53,3 +53,5 @@ SENTRY_TRACES_SAMPLE_RATE=0.1 VITE_APP_NAME="${APP_NAME}" VITE_APP_ENV="${APP_ENV}" VITE_SENTRY_DSN_PUBLIC="" + +HEALTH_CHECK_TO_ADDRESS= diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 8604526..20596dc 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -7,6 +7,16 @@ use Illuminate\Support\Facades\Vite; use Illuminate\Support\ServiceProvider; use Laravel\Pulse\Facades\Pulse; +use Spatie\Health\Checks\Checks\DatabaseCheck; +use Spatie\Health\Checks\Checks\DatabaseConnectionCountCheck; +use Spatie\Health\Checks\Checks\DebugModeCheck; +use Spatie\Health\Checks\Checks\EnvironmentCheck; +use Spatie\Health\Checks\Checks\HorizonCheck; +use Spatie\Health\Checks\Checks\RedisCheck; +use Spatie\Health\Checks\Checks\RedisMemoryUsageCheck; +use Spatie\Health\Checks\Checks\UsedDiskSpaceCheck; +use Spatie\Health\Facades\Health; +use Spatie\SecurityAdvisoriesHealthCheck\SecurityAdvisoriesCheck; class AppServiceProvider extends ServiceProvider { @@ -24,6 +34,20 @@ public function boot(): void Vite::prefetch(concurrency: 3); + Health::checks([ + UsedDiskSpaceCheck::new(), + DatabaseCheck::new(), + DatabaseConnectionCountCheck::new() + ->warnWhenMoreConnectionsThan(50) + ->failWhenMoreConnectionsThan(100), + DebugModeCheck::new(), + EnvironmentCheck::new(), + RedisCheck::new(), + RedisMemoryUsageCheck::new(), + HorizonCheck::new(), + SecurityAdvisoriesCheck::new(), + ]); + // @codeCoverageIgnoreStart Pulse::users(function ($ids) { return User::findMany($ids)->map(fn ($user) => [ diff --git a/composer.json b/composer.json index cfaa2ec..e111c28 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,9 @@ "laravel/tinker": "^2.9", "pxlrbt/filament-environment-indicator": "^2.0", "sentry/sentry-laravel": "^4.7", + "spatie/laravel-health": "^1.31", "spatie/laravel-permission": "^6.9", + "spatie/security-advisories-health-check": "^1.2", "stephenjude/filament-debugger": "^3.1", "tightenco/ziggy": "^2.3" }, diff --git a/composer.lock b/composer.lock index 5f2987d..f54b06a 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": "189412ae0b0c66d07461f40df769c651", + "content-hash": "58b3774fc13b0baa34eaf4121db7fa48", "packages": [ { "name": "althinect/filament-spatie-roles-permissions", @@ -414,6 +414,87 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, { "name": "danharrin/date-format-converter", "version": "v0.3.1", @@ -5576,6 +5657,82 @@ ], "time": "2024-09-20T14:00:15+00:00" }, + { + "name": "spatie/enum", + "version": "3.13.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/enum.git", + "reference": "f1a0f464ba909491a53e60a955ce84ad7cd93a2c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/enum/zipball/f1a0f464ba909491a53e60a955ce84ad7cd93a2c", + "reference": "f1a0f464ba909491a53e60a955ce84ad7cd93a2c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.0" + }, + "require-dev": { + "fakerphp/faker": "^1.9.1", + "larapack/dd": "^1.1", + "phpunit/phpunit": "^9.0", + "vimeo/psalm": "^4.3" + }, + "suggest": { + "fakerphp/faker": "To use the enum faker provider", + "phpunit/phpunit": "To use the enum assertions" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Enum\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Roose", + "email": "brent@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Tom Witkowski", + "email": "dev@gummibeer.de", + "homepage": "https://gummibeer.de", + "role": "Developer" + } + ], + "description": "PHP Enums", + "homepage": "https://github.com/spatie/enum", + "keywords": [ + "enum", + "enumerable", + "spatie" + ], + "support": { + "docs": "https://docs.spatie.be/enum", + "issues": "https://github.com/spatie/enum/issues", + "source": "https://github.com/spatie/enum" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-04-22T08:51:55+00:00" + }, { "name": "spatie/invade", "version": "2.1.0", @@ -5635,6 +5792,100 @@ ], "time": "2024-05-17T09:06:10+00:00" }, + { + "name": "spatie/laravel-health", + "version": "1.31.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-health.git", + "reference": "0d3b665b60e6821397367cd7f6230fa6fc68b2c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-health/zipball/0d3b665b60e6821397367cd7f6230fa6fc68b2c8", + "reference": "0d3b665b60e6821397367cd7f6230fa6fc68b2c8", + "shasum": "" + }, + "require": { + "dragonmantank/cron-expression": "^3.3.1", + "guzzlehttp/guzzle": "^6.5|^7.4.5|^7.2", + "illuminate/console": "^8.75|^9.0|^10.0|^11.0", + "illuminate/contracts": "^8.75|^9.0|^10.0|^11.0", + "illuminate/database": "^8.75|^9.0|^10.0|^11.0", + "illuminate/notifications": "^8.75|^9.0|^10.0|^11.0", + "illuminate/support": "^8.75|^9.0|^10.0|^11.0", + "laravel/serializable-closure": "^1.3|^2.0", + "nunomaduro/termwind": "^1.0|^2.0", + "php": "^8.0", + "spatie/enum": "^3.13", + "spatie/laravel-package-tools": "^1.12.1", + "spatie/regex": "^3.1.1|^3.1", + "spatie/temporary-directory": "^2.2", + "symfony/process": "^5.4|^6.0|^7.0" + }, + "require-dev": { + "larastan/larastan": "^1.0.3|^2.4", + "laravel/horizon": "^5.9.10", + "laravel/slack-notification-channel": "^2.4|^3.2", + "nunomaduro/collision": "^5.10|^6.2.1|^6.1|^8.0", + "orchestra/testbench": "^6.23|^7.6|^8.0|^9.0", + "pestphp/pest": "^1.21.3|^2.34", + "pestphp/pest-plugin-laravel": "^1.2|^2.3", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.1.1", + "phpunit/phpunit": "^9.5.21|^9.5.10|^10.5", + "spatie/laravel-ray": "^1.30", + "spatie/pest-plugin-snapshots": "^1.1|^2.1", + "spatie/pest-plugin-test-time": "^1.1.1|^1.1|^2.0", + "spatie/test-time": "^1.3" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Health": "Spatie\\Health\\Facades\\Health" + }, + "providers": [ + "Spatie\\Health\\HealthServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\Health\\": "src", + "Spatie\\Health\\Database\\Factories\\": "database/factories" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Monitor the health of a Laravel application", + "homepage": "https://github.com/spatie/laravel-health", + "keywords": [ + "laravel", + "laravel-health", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/laravel-health/tree/1.31.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-12-09T14:17:48+00:00" + }, { "name": "spatie/laravel-package-tools", "version": "1.16.5", @@ -5777,6 +6028,267 @@ ], "time": "2024-06-22T23:04:52+00:00" }, + { + "name": "spatie/packagist-api", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/packagist-api.git", + "reference": "4d125fec1c937ba8485a5b7e026532508314fc32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/packagist-api/zipball/4d125fec1c937ba8485a5b7e026532508314fc32", + "reference": "4d125fec1c937ba8485a5b7e026532508314fc32", + "shasum": "" + }, + "require": { + "composer/semver": "^1.0|^2.0|^3.0", + "ext-json": "*", + "guzzlehttp/guzzle": "^7.0", + "php": "^7.3|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.4", + "spatie/phpunit-snapshot-assertions": "^4.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Packagist\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Jolita Grazyte", + "email": "jolita@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Mark Walet", + "homepage": "https://markwalet.me", + "role": "Developer" + } + ], + "description": "Fetch package info from Packagist", + "homepage": "https://github.com/spatie/packagist-api", + "keywords": [ + "api", + "packagist", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/packagist-api/tree/2.1.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-08-01T10:13:17+00:00" + }, + { + "name": "spatie/regex", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/regex.git", + "reference": "d543de2019a0068e7b80da0ba24f1c51c7469303" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/regex/zipball/d543de2019a0068e7b80da0ba24f1c51c7469303", + "reference": "d543de2019a0068e7b80da0ba24f1c51c7469303", + "shasum": "" + }, + "require": { + "php": "^8.0|^8.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Regex\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sebastian De Deyne", + "email": "sebastian@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A sane interface for php's built in preg_* functions", + "homepage": "https://github.com/spatie/regex", + "keywords": [ + "expression", + "expressions", + "regex", + "regular", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/regex/issues", + "source": "https://github.com/spatie/regex/tree/3.1.1" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2021-11-30T21:13:59+00:00" + }, + { + "name": "spatie/security-advisories-health-check", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/security-advisories-health-check.git", + "reference": "b98e85e9088039b6154bfb568770934dd46749f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/security-advisories-health-check/zipball/b98e85e9088039b6154bfb568770934dd46749f3", + "reference": "b98e85e9088039b6154bfb568770934dd46749f3", + "shasum": "" + }, + "require": { + "php": "^8.0", + "spatie/packagist-api": "^2.1" + }, + "require-dev": { + "nunomaduro/collision": "^6.1|^7.0|^8.0", + "pestphp/pest": "^1.21|^2.3", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5.10|^10.5", + "spatie/laravel-health": "^1.22.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\SecurityAdvisoriesHealthCheck\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "A Laravel Health check to security advisories for PHP packages", + "homepage": "https://github.com/spatie/security-advisories-health-check", + "keywords": [ + "laravel", + "security-advisories-health-check", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/security-advisories-health-check/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-06-05T14:41:26+00:00" + }, + { + "name": "spatie/temporary-directory", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/temporary-directory.git", + "reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a", + "reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\TemporaryDirectory\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily create, use and destroy temporary directories", + "homepage": "https://github.com/spatie/temporary-directory", + "keywords": [ + "php", + "spatie", + "temporary-directory" + ], + "support": { + "issues": "https://github.com/spatie/temporary-directory/issues", + "source": "https://github.com/spatie/temporary-directory/tree/2.2.1" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2023-12-25T11:46:58+00:00" + }, { "name": "stephenjude/filament-debugger", "version": "3.1.2", @@ -12636,12 +13148,12 @@ ], "aliases": [], "minimum-stability": "beta", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/config/health.php b/config/health.php new file mode 100644 index 0000000..97a11df --- /dev/null +++ b/config/health.php @@ -0,0 +1,127 @@ + [ + Spatie\Health\ResultStores\EloquentHealthResultStore::class => [ + 'connection' => env('HEALTH_DB_CONNECTION', env('DB_CONNECTION')), + 'model' => Spatie\Health\Models\HealthCheckResultHistoryItem::class, + 'keep_history_for_days' => 5, + ], + + /* + Spatie\Health\ResultStores\CacheHealthResultStore::class => [ + 'store' => 'file', + ], + + Spatie\Health\ResultStores\JsonFileHealthResultStore::class => [ + 'disk' => 's3', + 'path' => 'health.json', + ], + + Spatie\Health\ResultStores\InMemoryHealthResultStore::class, + */ + ], + + /* + * You can get notified when specific events occur. Out of the box you can use 'mail' and 'slack'. + * For Slack you need to install laravel/slack-notification-channel. + */ + 'notifications' => [ + /* + * Notifications will only get sent if this option is set to `true`. + */ + 'enabled' => true, + + 'notifications' => [ + Spatie\Health\Notifications\CheckFailedNotification::class => ['mail'], + ], + + /* + * Here you can specify the notifiable to which the notifications should be sent. The default + * notifiable will use the variables specified in this config file. + */ + 'notifiable' => Spatie\Health\Notifications\Notifiable::class, + + /* + * When checks start failing, you could potentially end up getting + * a notification every minute. + * + * With this setting, notifications are throttled. By default, you'll + * only get one notification per hour. + */ + 'throttle_notifications_for_minutes' => 60, + 'throttle_notifications_key' => 'health:latestNotificationSentAt:', + + 'mail' => [ + 'to' => env('HEALTH_CHECK_TO_ADDRESS', ''), + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], + ], + + 'slack' => [ + 'webhook_url' => env('HEALTH_SLACK_WEBHOOK_URL', ''), + + /* + * If this is set to null the default channel of the webhook will be used. + */ + 'channel' => null, + + 'username' => null, + + 'icon' => null, + ], + ], + + /* + * You can let Oh Dear monitor the results of all health checks. This way, you'll + * get notified of any problems even if your application goes totally down. Via + * Oh Dear, you can also have access to more advanced notification options. + */ + 'oh_dear_endpoint' => [ + 'enabled' => false, + + /* + * When this option is enabled, the checks will run before sending a response. + * Otherwise, we'll send the results from the last time the checks have run. + */ + 'always_send_fresh_results' => true, + + /* + * The secret that is displayed at the Application Health settings at Oh Dear. + */ + 'secret' => env('OH_DEAR_HEALTH_CHECK_SECRET'), + + /* + * The URL that should be configured in the Application health settings at Oh Dear. + */ + 'url' => '/oh-dear-health-check-results', + ], + + /* + * You can set a theme for the local results page + * + * - light: light mode + * - dark: dark mode + */ + 'theme' => 'light', + + /* + * When enabled, completed `HealthQueueJob`s will be displayed + * in Horizon's silenced jobs screen. + */ + 'silence_health_queue_job' => true, + + /* + * The response code to use for HealthCheckJsonResultsController when a health + * check has failed + */ + 'json_results_failure_status' => 200, +]; diff --git a/database/migrations/2024_12_12_212512_create_health_tables.php b/database/migrations/2024_12_12_212512_create_health_tables.php new file mode 100644 index 0000000..a064ed8 --- /dev/null +++ b/database/migrations/2024_12_12_212512_create_health_tables.php @@ -0,0 +1,36 @@ +getConnectionName(); + $tableName = EloquentHealthResultStore::getHistoryItemInstance()->getTable(); + + Schema::connection($connection)->create($tableName, function (Blueprint $table) { + $table->id(); + + $table->string('check_name'); + $table->string('check_label'); + $table->string('status'); + $table->text('notification_message')->nullable(); + $table->string('short_summary')->nullable(); + $table->json('meta'); + $table->timestamp('ended_at'); + $table->uuid('batch'); + + $table->timestamps(); + }); + + Schema::connection($connection)->table($tableName, function (Blueprint $table) { + $table->index('created_at'); + $table->index('batch'); + }); + } +}; diff --git a/routes/console.php b/routes/console.php index 22b26c3..f71f013 100644 --- a/routes/console.php +++ b/routes/console.php @@ -3,3 +3,4 @@ use Illuminate\Support\Facades\Schedule; Schedule::command('horizon:snapshot')->everyFiveMinutes(); +Schedule::command(\Spatie\Health\Commands\RunHealthChecksCommand::class)->everyMinute()->environments('production'); diff --git a/routes/web.php b/routes/web.php index b4a3a10..d2f86cc 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,6 +2,8 @@ use Illuminate\Support\Facades\Route; +Route::get('health', Spatie\Health\Http\Controllers\HealthCheckResultsController::class)->middleware(['auth']); + Route::controller(App\Http\Controllers\RegisterController::class) ->middleware(['guest']) ->group(function () {