Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for
## Foundational Context
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.

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

## Laravel Pint Code Formatter

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

=== pest/core rules ===

Expand Down
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for
## Foundational Context
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.

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

## Laravel Pint Code Formatter

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

=== pest/core rules ===

Expand Down
6 changes: 3 additions & 3 deletions GEMINI.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for
## Foundational Context
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.

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

## Laravel Pint Code Formatter

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

=== pest/core rules ===

Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,41 @@ php artisan key:generate --show
docker run --rm ghcr.io/cboxdk/websocket-server:latest php artisan key:generate --show
```

### Seed Apps From Env

For declarative, GitOps-friendly bootstrap, pass `REVERB_SEED_APPS` as a JSON
array. At pod start the seed runs as a oneshot after `migrate` and before
`php-fpm`/`reverb`, so the instance comes up with its apps already provisioned
— no race with the admin API.

```bash
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"]}]'
```

Seed is idempotent: re-runs upsert rows by `id`, so secret rotation or
allowed-origin changes are just an env-var change + redeploy. Apps created
out-of-band via the admin API are left untouched.

Per-entry schema:

| Field | Type | Required | Default |
|---|---|---|---|
| `id` | string | yes | — |
| `key` | string (unique) | yes | — |
| `secret` | string | yes | — |
| `name` | string | yes | — |
| `allowed_origins` | string[] | no | `["*"]` |
| `enable_client_messages` | bool | no | `false` |
| `max_connections` | int\|null | no | `null` |
| `max_message_size` | int | no | `10000` |
| `options` | object | no | `null` |

Or invoke manually:

```bash
php artisan reverb:seed-from-env
```

## API Reference

All API endpoints require the `Authorization: Bearer {API_ADMIN_TOKEN}` header.
Expand Down
141 changes: 141 additions & 0 deletions app/Console/Commands/SeedReverbAppsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Models\ReverbApplication;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use JsonException;
use Throwable;

/**
* Seed reverb_applications rows from the REVERB_SEED_APPS env var.
*
* The env carries a JSON array of app definitions. Run as a oneshot at
* pod boot (after migrate, before php-fpm/reverb) so the instance comes
* up with its apps already provisioned — no race with the admin API.
*
* Idempotent: existing rows are upserted by id, so secret rotation or
* allowed-origin changes just need a redeploy with new env. Rows that
* exist in the DB but are absent from the env are left alone — the
* admin API is the authority for ad-hoc app creation, the env is only
* for the bootstrap set.
*
* Exit codes:
* 0 — success (including no env set / empty array)
* 1 — invalid JSON or schema, or write failure
*
* Schema per entry:
* id string, required (primary key)
* key string, required (must be unique across apps)
* secret string, required
* name string, required
* allowed_origins array of string, optional, default ["*"]
* enable_client_messages bool, optional, default false
* max_connections int|null, optional
* max_message_size int, optional, default 10000
* options object, optional
*/
class SeedReverbAppsCommand extends Command
{
protected $signature = 'reverb:seed-from-env';

protected $description = 'Seed reverb_applications from the REVERB_SEED_APPS env var (JSON array).';

public function handle(): int
{
$raw = (string) env('REVERB_SEED_APPS', '');
if (trim($raw) === '') {
$this->info('REVERB_SEED_APPS empty — nothing to seed.');

return self::SUCCESS;
}

try {
$decoded = json_decode($raw, associative: true, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
$this->error('REVERB_SEED_APPS is not valid JSON: '.$e->getMessage());

return self::FAILURE;
}

if (! is_array($decoded)) {
$this->error('REVERB_SEED_APPS must decode to a JSON array.');

return self::FAILURE;
}

$validator = Validator::make(['apps' => $decoded], [
'apps' => ['array'],
'apps.*.id' => ['required', 'string'],
'apps.*.key' => ['required', 'string'],
'apps.*.secret' => ['required', 'string'],
'apps.*.name' => ['required', 'string'],
'apps.*.allowed_origins' => ['sometimes', 'array'],
'apps.*.allowed_origins.*' => ['string'],
'apps.*.enable_client_messages' => ['sometimes', 'boolean'],
'apps.*.max_connections' => ['sometimes', 'nullable', 'integer'],
'apps.*.max_message_size' => ['sometimes', 'integer'],
'apps.*.options' => ['sometimes', 'array'],
]);

if ($validator->fails()) {
$this->error('REVERB_SEED_APPS schema invalid:');
foreach ($validator->errors()->all() as $err) {
$this->line(' - '.$err);
}

return self::FAILURE;
}

$created = 0;
$updated = 0;

try {
foreach ($decoded as $entry) {
$attrs = [
'key' => $entry['key'],
'secret' => $entry['secret'],
'name' => $entry['name'],
'allowed_origins' => $entry['allowed_origins'] ?? ['*'],
'enable_client_messages' => $entry['enable_client_messages'] ?? false,
'max_connections' => $entry['max_connections'] ?? null,
'max_message_size' => $entry['max_message_size'] ?? 10000,
'options' => $entry['options'] ?? null,
];

$existing = ReverbApplication::query()->find($entry['id']);

if ($existing === null) {
ReverbApplication::query()->create(['id' => $entry['id'], ...$attrs]);
$created++;
$this->info("Created app [{$entry['id']}]");
} else {
$existing->fill($attrs);
if ($existing->isDirty()) {
$existing->save();
$updated++;
$this->info("Updated app [{$entry['id']}]");
} else {
$this->line("App [{$entry['id']}] already up-to-date");
}
}
}
} catch (Throwable $e) {
$this->error('Failed to upsert app: '.$e->getMessage());

return self::FAILURE;
}

$this->info(sprintf(
'Seed complete — %d created, %d updated, %d total in env.',
$created,
$updated,
count($decoded),
));

return self::SUCCESS;
}
}
15 changes: 15 additions & 0 deletions app/Reverb/DatabaseApplicationProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,24 @@ public function findByKey(string $key): Application

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

$acceptClientEventsFrom = $options['accept_client_events_from']
?? ($app->enable_client_messages ? 'all' : 'none');

return new Application(
$app->id,
$app->key,
Expand All @@ -69,6 +82,8 @@ protected function createApplication(ReverbApplication $app): Application
$app->allowed_origins ?? ['*'],
$app->max_message_size ?? 10_000,
$app->max_connections,
$acceptClientEventsFrom,
$options['rate_limiting'] ?? null,
$options,
);
}
Expand Down
Loading
Loading