Skip to content

Commit cbdcc29

Browse files
Merge pull request #1 from cboxdk/feature/seed-apps-from-env
feat: seed Reverb apps from REVERB_SEED_APPS env at boot
2 parents 2e43b22 + 27a7995 commit cbdcc29

9 files changed

Lines changed: 1116 additions & 588 deletions

File tree

AGENTS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for
88
## Foundational Context
99
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
1010

11-
- php - 8.4.17
11+
- php - 8.5.5
1212
- laravel/framework (LARAVEL) - v12
1313
- laravel/prompts (PROMPTS) - v0
1414
- laravel/reverb (REVERB) - v1
@@ -185,8 +185,8 @@ protected function isAccessible(User $user, ?string $path = null): bool
185185

186186
## Laravel Pint Code Formatter
187187

188-
- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style.
189-
- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues.
188+
- You must run `vendor/bin/pint --dirty --format agent` before finalizing changes to ensure your code matches the project's expected style.
189+
- Do not run `vendor/bin/pint --test --format agent`, simply run `vendor/bin/pint --format agent` to fix any formatting issues.
190190

191191
=== pest/core rules ===
192192

CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for
88
## Foundational Context
99
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
1010

11-
- php - 8.4.17
11+
- php - 8.5.5
1212
- laravel/framework (LARAVEL) - v12
1313
- laravel/prompts (PROMPTS) - v0
1414
- laravel/reverb (REVERB) - v1
@@ -185,8 +185,8 @@ protected function isAccessible(User $user, ?string $path = null): bool
185185

186186
## Laravel Pint Code Formatter
187187

188-
- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style.
189-
- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues.
188+
- You must run `vendor/bin/pint --dirty --format agent` before finalizing changes to ensure your code matches the project's expected style.
189+
- Do not run `vendor/bin/pint --test --format agent`, simply run `vendor/bin/pint --format agent` to fix any formatting issues.
190190

191191
=== pest/core rules ===
192192

GEMINI.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for
88
## Foundational Context
99
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
1010

11-
- php - 8.4.17
11+
- php - 8.5.5
1212
- laravel/framework (LARAVEL) - v12
1313
- laravel/prompts (PROMPTS) - v0
1414
- laravel/reverb (REVERB) - v1
@@ -185,8 +185,8 @@ protected function isAccessible(User $user, ?string $path = null): bool
185185

186186
## Laravel Pint Code Formatter
187187

188-
- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style.
189-
- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues.
188+
- You must run `vendor/bin/pint --dirty --format agent` before finalizing changes to ensure your code matches the project's expected style.
189+
- Do not run `vendor/bin/pint --test --format agent`, simply run `vendor/bin/pint --format agent` to fix any formatting issues.
190190

191191
=== pest/core rules ===
192192

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,41 @@ php artisan key:generate --show
132132
docker run --rm ghcr.io/cboxdk/websocket-server:latest php artisan key:generate --show
133133
```
134134

135+
### Seed Apps From Env
136+
137+
For declarative, GitOps-friendly bootstrap, pass `REVERB_SEED_APPS` as a JSON
138+
array. At pod start the seed runs as a oneshot after `migrate` and before
139+
`php-fpm`/`reverb`, so the instance comes up with its apps already provisioned
140+
— no race with the admin API.
141+
142+
```bash
143+
REVERB_SEED_APPS='[{"id":"app-1","key":"app-1-key","secret":"app-1-secret-min-32-characters-long","name":"App One","allowed_origins":["https://app-one.example.com"]}]'
144+
```
145+
146+
Seed is idempotent: re-runs upsert rows by `id`, so secret rotation or
147+
allowed-origin changes are just an env-var change + redeploy. Apps created
148+
out-of-band via the admin API are left untouched.
149+
150+
Per-entry schema:
151+
152+
| Field | Type | Required | Default |
153+
|---|---|---|---|
154+
| `id` | string | yes ||
155+
| `key` | string (unique) | yes ||
156+
| `secret` | string | yes ||
157+
| `name` | string | yes ||
158+
| `allowed_origins` | string[] | no | `["*"]` |
159+
| `enable_client_messages` | bool | no | `false` |
160+
| `max_connections` | int\|null | no | `null` |
161+
| `max_message_size` | int | no | `10000` |
162+
| `options` | object | no | `null` |
163+
164+
Or invoke manually:
165+
166+
```bash
167+
php artisan reverb:seed-from-env
168+
```
169+
135170
## API Reference
136171

137172
All API endpoints require the `Authorization: Bearer {API_ADMIN_TOKEN}` header.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Console\Commands;
6+
7+
use App\Models\ReverbApplication;
8+
use Illuminate\Console\Command;
9+
use Illuminate\Support\Facades\Validator;
10+
use JsonException;
11+
use Throwable;
12+
13+
/**
14+
* Seed reverb_applications rows from the REVERB_SEED_APPS env var.
15+
*
16+
* The env carries a JSON array of app definitions. Run as a oneshot at
17+
* pod boot (after migrate, before php-fpm/reverb) so the instance comes
18+
* up with its apps already provisioned — no race with the admin API.
19+
*
20+
* Idempotent: existing rows are upserted by id, so secret rotation or
21+
* allowed-origin changes just need a redeploy with new env. Rows that
22+
* exist in the DB but are absent from the env are left alone — the
23+
* admin API is the authority for ad-hoc app creation, the env is only
24+
* for the bootstrap set.
25+
*
26+
* Exit codes:
27+
* 0 — success (including no env set / empty array)
28+
* 1 — invalid JSON or schema, or write failure
29+
*
30+
* Schema per entry:
31+
* id string, required (primary key)
32+
* key string, required (must be unique across apps)
33+
* secret string, required
34+
* name string, required
35+
* allowed_origins array of string, optional, default ["*"]
36+
* enable_client_messages bool, optional, default false
37+
* max_connections int|null, optional
38+
* max_message_size int, optional, default 10000
39+
* options object, optional
40+
*/
41+
class SeedReverbAppsCommand extends Command
42+
{
43+
protected $signature = 'reverb:seed-from-env';
44+
45+
protected $description = 'Seed reverb_applications from the REVERB_SEED_APPS env var (JSON array).';
46+
47+
public function handle(): int
48+
{
49+
$raw = (string) env('REVERB_SEED_APPS', '');
50+
if (trim($raw) === '') {
51+
$this->info('REVERB_SEED_APPS empty — nothing to seed.');
52+
53+
return self::SUCCESS;
54+
}
55+
56+
try {
57+
$decoded = json_decode($raw, associative: true, flags: JSON_THROW_ON_ERROR);
58+
} catch (JsonException $e) {
59+
$this->error('REVERB_SEED_APPS is not valid JSON: '.$e->getMessage());
60+
61+
return self::FAILURE;
62+
}
63+
64+
if (! is_array($decoded)) {
65+
$this->error('REVERB_SEED_APPS must decode to a JSON array.');
66+
67+
return self::FAILURE;
68+
}
69+
70+
$validator = Validator::make(['apps' => $decoded], [
71+
'apps' => ['array'],
72+
'apps.*.id' => ['required', 'string'],
73+
'apps.*.key' => ['required', 'string'],
74+
'apps.*.secret' => ['required', 'string'],
75+
'apps.*.name' => ['required', 'string'],
76+
'apps.*.allowed_origins' => ['sometimes', 'array'],
77+
'apps.*.allowed_origins.*' => ['string'],
78+
'apps.*.enable_client_messages' => ['sometimes', 'boolean'],
79+
'apps.*.max_connections' => ['sometimes', 'nullable', 'integer'],
80+
'apps.*.max_message_size' => ['sometimes', 'integer'],
81+
'apps.*.options' => ['sometimes', 'array'],
82+
]);
83+
84+
if ($validator->fails()) {
85+
$this->error('REVERB_SEED_APPS schema invalid:');
86+
foreach ($validator->errors()->all() as $err) {
87+
$this->line(' - '.$err);
88+
}
89+
90+
return self::FAILURE;
91+
}
92+
93+
$created = 0;
94+
$updated = 0;
95+
96+
try {
97+
foreach ($decoded as $entry) {
98+
$attrs = [
99+
'key' => $entry['key'],
100+
'secret' => $entry['secret'],
101+
'name' => $entry['name'],
102+
'allowed_origins' => $entry['allowed_origins'] ?? ['*'],
103+
'enable_client_messages' => $entry['enable_client_messages'] ?? false,
104+
'max_connections' => $entry['max_connections'] ?? null,
105+
'max_message_size' => $entry['max_message_size'] ?? 10000,
106+
'options' => $entry['options'] ?? null,
107+
];
108+
109+
$existing = ReverbApplication::query()->find($entry['id']);
110+
111+
if ($existing === null) {
112+
ReverbApplication::query()->create(['id' => $entry['id'], ...$attrs]);
113+
$created++;
114+
$this->info("Created app [{$entry['id']}]");
115+
} else {
116+
$existing->fill($attrs);
117+
if ($existing->isDirty()) {
118+
$existing->save();
119+
$updated++;
120+
$this->info("Updated app [{$entry['id']}]");
121+
} else {
122+
$this->line("App [{$entry['id']}] already up-to-date");
123+
}
124+
}
125+
}
126+
} catch (Throwable $e) {
127+
$this->error('Failed to upsert app: '.$e->getMessage());
128+
129+
return self::FAILURE;
130+
}
131+
132+
$this->info(sprintf(
133+
'Seed complete — %d created, %d updated, %d total in env.',
134+
$created,
135+
$updated,
136+
count($decoded),
137+
));
138+
139+
return self::SUCCESS;
140+
}
141+
}

app/Reverb/DatabaseApplicationProvider.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,24 @@ public function findByKey(string $key): Application
5555

5656
/**
5757
* Create an Application instance from model.
58+
*
59+
* Reverb 1.x's Application constructor accepts:
60+
* id, key, secret, pingInterval, activityTimeout, allowedOrigins,
61+
* maxMessageSize, ?maxConnections, acceptClientEventsFrom,
62+
* ?rateLimiting, options
63+
*
64+
* We honour the legacy `enable_client_messages` boolean by mapping
65+
* true → "all", false → "none". Override per-app by setting
66+
* `options.accept_client_events_from` (one of "all" | "members" | "none")
67+
* which matches Reverb's native config shape.
5868
*/
5969
protected function createApplication(ReverbApplication $app): Application
6070
{
6171
$options = $app->options ?? [];
6272

73+
$acceptClientEventsFrom = $options['accept_client_events_from']
74+
?? ($app->enable_client_messages ? 'all' : 'none');
75+
6376
return new Application(
6477
$app->id,
6578
$app->key,
@@ -69,6 +82,8 @@ protected function createApplication(ReverbApplication $app): Application
6982
$app->allowed_origins ?? ['*'],
7083
$app->max_message_size ?? 10_000,
7184
$app->max_connections,
85+
$acceptClientEventsFrom,
86+
$options['rate_limiting'] ?? null,
7287
$options,
7388
);
7489
}

0 commit comments

Comments
 (0)