feat(auth): notify MapleDeploy on first-user registration
All checks were successful
Build MapleDeploy Coolify Image / build (push) Successful in 58s

After the first user registers, clears the setup token and dispatches
NotifySetupCompleteJob to POST the token to MapleDeploy's callback URL.
Adds setup_callback_url column to instance_settings.
This commit is contained in:
rosslh 2026-02-22 00:42:22 -05:00
parent 5904d3561f
commit 0f8e3abd77
4 changed files with 96 additions and 1 deletions

View file

@ -2,6 +2,7 @@
namespace App\Actions\Fortify; namespace App\Actions\Fortify;
use App\Jobs\NotifySetupCompleteJob;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
@ -56,7 +57,18 @@ public function create(array $input): User
// Disable registration after first user is created // Disable registration after first user is created
$settings = instanceSettings(); $settings = instanceSettings();
$settings->is_registration_enabled = false; $settings->is_registration_enabled = false;
// MapleDeploy: notify control plane that setup is complete
// Capture token before clearing so the job can authenticate with it
$callbackUrl = $settings->setup_callback_url;
$token = $settings->setup_token;
$settings->setup_token = null;
$settings->setup_callback_url = null;
$settings->save(); $settings->save();
if ($callbackUrl && $token) {
NotifySetupCompleteJob::dispatch($token, $callbackUrl);
}
} else { } else {
$user = User::create([ $user = User::create([
'name' => $input['name'], 'name' => $input['name'],

View file

@ -0,0 +1,55 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
/**
* Notify MapleDeploy that the first user has registered on this Coolify instance.
*
* Sends the setup token as a Bearer token so MapleDeploy can verify authenticity
* and clear its stored copy. The token acts as a one-time shared secret.
*/
class NotifySetupCompleteJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 5;
public array $backoff = [10, 30, 60, 120, 300];
public int $maxExceptions = 5;
public function __construct(
public string $setupToken,
public string $callbackUrl
) {
$this->onQueue('high');
}
public function handle(): void
{
$response = Http::withToken($this->setupToken)
->timeout(15)
->post($this->callbackUrl);
if (! $response->successful()) {
Log::warning('Setup-complete callback failed', [
'status' => $response->status(),
'url' => $this->callbackUrl,
]);
// Throw so the job retries
throw new \RuntimeException(
"Setup-complete callback returned HTTP {$response->status()}"
);
}
}
}

View file

@ -3,7 +3,7 @@
return [ return [
// MapleDeploy branding: registry pointed to Forgejo, auto-update disabled by default // MapleDeploy branding: registry pointed to Forgejo, auto-update disabled by default
'coolify' => [ 'coolify' => [
'version' => '4.0.0-beta.463.10', 'version' => '4.0.0-beta.463.11',
'helper_version' => '1.0.12', 'helper_version' => '1.0.12',
'realtime_version' => '1.0.10', 'realtime_version' => '1.0.10',
'self_hosted' => env('SELF_HOSTED', true), 'self_hosted' => env('SELF_HOSTED', true),

View file

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('instance_settings', function (Blueprint $table) {
$table->string('setup_callback_url')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('instance_settings', function (Blueprint $table) {
$table->dropColumn('setup_callback_url');
});
}
};