app->singleton(PipelineRegistry::class, fn () => new PipelineRegistry( config('provisioning.pipelines', []), )); // Real I/O implementations; tests swap in fakes via app()->instance(). $this->app->bind(RemoteShell::class, PhpseclibRemoteShell::class); $this->app->bind(WireguardHub::class, LocalWireguardHub::class); $this->app->bind(ProxmoxClient::class, HttpProxmoxClient::class); $this->app->bind(HetznerDnsClient::class, HttpHetznerDnsClient::class); $this->app->bind(TraefikWriter::class, SshTraefikWriter::class); $this->app->bind(MonitoringClient::class, HttpMonitoringClient::class); } /** * Bootstrap any application services. */ public function boot(): void { // Suppress a queued maintenance announcement if its window was cancelled // before the job runs — otherwise a delayed announcement could arrive for // an already-cancelled window. Returning false aborts the send. Event::listen(MessageSending::class, function (MessageSending $event) { $header = $event->message->getHeaders()->get('X-CP-Notification'); if ($header === null) { return null; } $notification = MaintenanceNotification::query()->with('window')->find((int) $header->getBodyAsString()); if ($notification?->event === 'announcement' && $notification->window?->state === 'cancelled') { return false; // window cancelled meanwhile — do not deliver } return null; }); // Stamp a maintenance-notification ledger row as delivered only once the // mail is actually sent (the X-CP-Notification header carries the id). // Until then sent_at stays null → the row is a retryable marker. Event::listen(MessageSent::class, function (MessageSent $event) { $header = $event->message->getHeaders()->get('X-CP-Notification'); if ($header === null) { return; } MaintenanceNotification::query()->whereKey((int) $header->getBodyAsString())->update(['sent_at' => now()]); }); } }