Skip to content

Commit 2ae5449

Browse files
authored
feat(reverb): implement reverb health check + env variables generator (EC-194)
* feat(reverb): implement reverb health check + env variables generator * chore(reverb): rename command & signature, use custom health config in EclipseServiceProvider
1 parent 579a320 commit 2ae5449

4 files changed

Lines changed: 382 additions & 0 deletions

File tree

config/health.php

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<?php
2+
3+
return [
4+
/*
5+
* A result store is responsible for saving the results of the checks. The
6+
* `EloquentHealthResultStore` will save results in the database. You
7+
* can use multiple stores at the same time.
8+
*/
9+
'result_stores' => [
10+
Spatie\Health\ResultStores\EloquentHealthResultStore::class => [
11+
'connection' => env('HEALTH_DB_CONNECTION', env('DB_CONNECTION')),
12+
'model' => Spatie\Health\Models\HealthCheckResultHistoryItem::class,
13+
'keep_history_for_days' => 5,
14+
],
15+
16+
/*
17+
Spatie\Health\ResultStores\CacheHealthResultStore::class => [
18+
'store' => 'file',
19+
],
20+
21+
Spatie\Health\ResultStores\JsonFileHealthResultStore::class => [
22+
'disk' => 's3',
23+
'path' => 'health.json',
24+
],
25+
26+
Spatie\Health\ResultStores\InMemoryHealthResultStore::class,
27+
*/
28+
],
29+
30+
/*
31+
* You can get notified when specific events occur. Out of the box you can use 'mail' and 'slack'.
32+
* For Slack you need to install laravel/slack-notification-channel.
33+
*/
34+
'notifications' => [
35+
/*
36+
* Notifications will only get sent if this option is set to `true`.
37+
*/
38+
'enabled' => true,
39+
40+
'notifications' => [
41+
Spatie\Health\Notifications\CheckFailedNotification::class => ['mail'],
42+
],
43+
44+
/*
45+
* Here you can specify the notifiable to which the notifications should be sent. The default
46+
* notifiable will use the variables specified in this config file.
47+
*/
48+
'notifiable' => Spatie\Health\Notifications\Notifiable::class,
49+
50+
/*
51+
* When checks start failing, you could potentially end up getting
52+
* a notification every minute.
53+
*
54+
* With this setting, notifications are throttled. By default, you'll
55+
* only get one notification per hour.
56+
*/
57+
'throttle_notifications_for_minutes' => 60,
58+
'throttle_notifications_key' => 'health:latestNotificationSentAt:',
59+
60+
'mail' => [
61+
'to' => 'your@example.com',
62+
63+
'from' => [
64+
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
65+
'name' => env('MAIL_FROM_NAME', 'Example'),
66+
],
67+
],
68+
69+
'slack' => [
70+
'webhook_url' => env('HEALTH_SLACK_WEBHOOK_URL', ''),
71+
72+
/*
73+
* If this is set to null the default channel of the webhook will be used.
74+
*/
75+
'channel' => null,
76+
77+
'username' => null,
78+
79+
'icon' => null,
80+
],
81+
],
82+
83+
/*
84+
* You can let Oh Dear monitor the results of all health checks. This way, you'll
85+
* get notified of any problems even if your application goes totally down. Via
86+
* Oh Dear, you can also have access to more advanced notification options.
87+
*/
88+
'oh_dear_endpoint' => [
89+
'enabled' => false,
90+
91+
/*
92+
* When this option is enabled, the checks will run before sending a response.
93+
* Otherwise, we'll send the results from the last time the checks have run.
94+
*/
95+
'always_send_fresh_results' => true,
96+
97+
/*
98+
* The secret that is displayed at the Application Health settings at Oh Dear.
99+
*/
100+
'secret' => env('OH_DEAR_HEALTH_CHECK_SECRET'),
101+
102+
/*
103+
* The URL that should be configured in the Application health settings at Oh Dear.
104+
*/
105+
'url' => '/oh-dear-health-check-results',
106+
],
107+
108+
/*
109+
* You can specify a heartbeat URL for the Horizon check.
110+
* This URL will be pinged if the Horizon check is successful.
111+
* This way you can get notified if Horizon goes down.
112+
*/
113+
'horizon' => [
114+
'heartbeat_url' => env('HORIZON_HEARTBEAT_URL', null),
115+
],
116+
117+
/*
118+
* You can specify a heartbeat URL for the Schedule check.
119+
* This URL will be pinged if the Schedule check is successful.
120+
* This way you can get notified if the schedule fails to run.
121+
*/
122+
'schedule' => [
123+
'heartbeat_url' => env('SCHEDULE_HEARTBEAT_URL', null),
124+
],
125+
126+
/*
127+
* You can specify a heartbeat URL for the Reverb check.
128+
* This URL will be pinged if the Reverb check is successful.
129+
* This way you can get notified if Reverb goes down.
130+
*/
131+
'reverb' => [
132+
'heartbeat_url' => env('REVERB_HEARTBEAT_URL', null),
133+
],
134+
135+
/*
136+
* You can set a theme for the local results page
137+
*
138+
* - light: light mode
139+
* - dark: dark mode
140+
*/
141+
'theme' => 'light',
142+
143+
/*
144+
* When enabled, completed `HealthQueueJob`s will be displayed
145+
* in Horizon's silenced jobs screen.
146+
*/
147+
'silence_health_queue_job' => true,
148+
149+
/*
150+
* The response code to use for HealthCheckJsonResultsController when a health
151+
* check has failed
152+
*/
153+
'json_results_failure_status' => 200,
154+
155+
/*
156+
* You can specify a secret token that needs to be sent in the X-Secret-Token for secured access.
157+
*/
158+
'secret_token' => env('HEALTH_SECRET_TOKEN') ?? null,
159+
160+
/**
161+
* By default, conditionally skipped health checks are treated as failures.
162+
* You can override this behavior by uncommenting the configuration below.
163+
*
164+
* @link https://spatie.be/docs/laravel-health/v1/basic-usage/conditionally-running-or-modifying-checks
165+
*/
166+
// 'treat_skipped_as_failure' => false
167+
];
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
namespace Eclipse\Core\Console\Commands;
4+
5+
use Illuminate\Console\Command;
6+
use Illuminate\Support\Facades\File;
7+
use Illuminate\Support\Str;
8+
9+
/**
10+
* Generate Reverb WebSocket server credentials for Laravel Broadcasting.
11+
*
12+
* This command generates the required credentials for Laravel Reverb WebSocket server:
13+
* - REVERB_APP_ID: Random 6-digit number (100,000 - 999,999)
14+
* - REVERB_APP_KEY: Random 20-character lowercase string
15+
* - REVERB_APP_SECRET: Random 20-character lowercase string
16+
*
17+
* The credentials are automatically added to the .env file. If credentials already exist,
18+
* use the --force flag to overwrite them.
19+
*
20+
* This replaces the need to run `php artisan install:broadcasting` for new projects,
21+
* ensuring Reverb is configured out-of-the-box.
22+
*
23+
* @example php artisan eclipse:setup:reverb
24+
* @example php artisan eclipse:setup:reverb --force
25+
*/
26+
class SetupReverb extends Command
27+
{
28+
/**
29+
* The name and signature of the console command.
30+
*
31+
* @var string
32+
*/
33+
protected $signature = 'eclipse:setup:reverb
34+
{--force : Force overwrite existing credentials}';
35+
36+
/**
37+
* The console command description.
38+
*
39+
* @var string
40+
*/
41+
protected $description = 'Generate random Reverb credentials (APP_ID, APP_KEY, APP_SECRET) for WebSocket broadcasting';
42+
43+
/**
44+
* Execute the console command.
45+
*
46+
* Generates new Reverb credentials and updates the .env file.
47+
* Uses the same generation logic as Laravel Reverb's install command.
48+
*
49+
* @return int Command exit code (SUCCESS or FAILURE)
50+
*/
51+
public function handle(): int
52+
{
53+
if (File::missing($env = app()->environmentFile())) {
54+
$this->components->error('Environment file not found.');
55+
56+
return self::FAILURE;
57+
}
58+
59+
$contents = File::get($env);
60+
61+
// Check if credentials already exist
62+
$hasCredentials = Str::contains($contents, 'REVERB_APP_ID=') &&
63+
! Str::contains($contents, 'REVERB_APP_ID=null');
64+
65+
if ($hasCredentials && ! $this->option('force')) {
66+
$this->components->warn('Reverb credentials already exist in .env file.');
67+
$this->components->info('Use --force to overwrite existing credentials.');
68+
69+
return self::SUCCESS;
70+
}
71+
72+
// Generate credentials using the same logic as Laravel Reverb
73+
$appId = random_int(100_000, 999_999);
74+
$appKey = Str::lower(Str::random(20));
75+
$appSecret = Str::lower(Str::random(20));
76+
77+
$this->updateEnvironmentFile($env, $contents, $appId, $appKey, $appSecret);
78+
79+
$this->components->info('Reverb credentials generated successfully:');
80+
$this->components->twoColumnDetail('REVERB_APP_ID', $appId);
81+
$this->components->twoColumnDetail('REVERB_APP_KEY', $appKey);
82+
$this->components->twoColumnDetail('REVERB_APP_SECRET', '***'.substr($appSecret, -4));
83+
84+
return self::SUCCESS;
85+
}
86+
87+
/**
88+
* Update the environment file with new Reverb credentials.
89+
*
90+
* This method handles both updating existing credentials and adding new ones.
91+
* It uses regex replacement for existing values and appends new lines for missing keys.
92+
*
93+
* @param string $envPath Path to the .env file
94+
* @param string $contents Current contents of the .env file
95+
* @param int $appId Generated Reverb application ID
96+
* @param string $appKey Generated Reverb application key
97+
* @param string $appSecret Generated Reverb application secret
98+
*/
99+
protected function updateEnvironmentFile(string $envPath, string $contents, int $appId, string $appKey, string $appSecret): void
100+
{
101+
$replacements = [
102+
'REVERB_APP_ID' => "REVERB_APP_ID={$appId}",
103+
'REVERB_APP_KEY' => "REVERB_APP_KEY={$appKey}",
104+
'REVERB_APP_SECRET' => "REVERB_APP_SECRET={$appSecret}",
105+
];
106+
107+
$newContents = $contents;
108+
109+
foreach ($replacements as $key => $value) {
110+
if (Str::contains($newContents, "{$key}=")) {
111+
// Replace existing value
112+
$newContents = preg_replace(
113+
"/^{$key}=.*$/m",
114+
$value,
115+
$newContents
116+
);
117+
} else {
118+
// Add new line if it doesn't exist
119+
$newContents = rtrim($newContents).PHP_EOL.$value.PHP_EOL;
120+
}
121+
}
122+
123+
File::put($envPath, $newContents);
124+
}
125+
}

src/EclipseServiceProvider.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use Eclipse\Core\Console\Commands\ClearCommand;
99
use Eclipse\Core\Console\Commands\DeployCommand;
1010
use Eclipse\Core\Console\Commands\PostComposerUpdate;
11+
use Eclipse\Core\Console\Commands\SetupReverb;
12+
use Eclipse\Core\Health\Checks\ReverbCheck;
1113
use Eclipse\Core\Models\Locale;
1214
use Eclipse\Core\Models\User;
1315
use Eclipse\Core\Models\User\Permission;
@@ -46,6 +48,7 @@ public function configurePackage(SpatiePackage|Package $package): void
4648
->hasCommands([
4749
ClearCommand::class,
4850
DeployCommand::class,
51+
SetupReverb::class,
4952
PostComposerUpdate::class,
5053
])
5154
->hasConfigFile([
@@ -58,6 +61,7 @@ public function configurePackage(SpatiePackage|Package $package): void
5861
'settings',
5962
'telescope',
6063
'themes',
64+
'health',
6165
])
6266
->hasViews()
6367
->hasSettings()
@@ -148,6 +152,7 @@ public function boot(): void
148152
->failWhenUsedSpaceIsAbovePercentage(90),
149153
CacheCheck::new(),
150154
HorizonCheck::new(),
155+
ReverbCheck::new(),
151156
RedisCheck::new(),
152157
ScheduleCheck::new(),
153158
SecurityAdvisoriesCheck::new(),

0 commit comments

Comments
 (0)