CluPilotCloud/app/Providers/AppServiceProvider.php

94 lines
3.9 KiB
PHP

<?php
namespace App\Providers;
use App\Provisioning\PipelineRegistry;
use App\Services\Dns\HetznerDnsClient;
use App\Services\Dns\HttpHetznerDnsClient;
use App\Services\Monitoring\HttpMonitoringClient;
use App\Services\Monitoring\MonitoringClient;
use App\Services\Proxmox\HttpProxmoxClient;
use App\Services\Proxmox\ProxmoxClient;
use App\Services\Ssh\PhpseclibRemoteShell;
use App\Services\Ssh\RemoteShell;
use App\Services\Traefik\SshTraefikWriter;
use App\Services\Traefik\TraefikWriter;
use App\Services\Wireguard\LocalWireguardHub;
use App\Services\Wireguard\WireguardHub;
use App\Mail\MaintenanceCancelledMail;
use App\Models\MaintenanceNotification;
use App\Services\Maintenance\MaintenanceNotifier;
use Illuminate\Mail\Events\MessageSending;
use Illuminate\Mail\Events\MessageSent;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
$this->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. If an
// announcement delivered for an already-cancelled window (the cancel race),
// queue a catch-up cancellation so that customer isn't left mis-informed.
Event::listen(MessageSent::class, function (MessageSent $event) {
$header = $event->message->getHeaders()->get('X-CP-Notification');
if ($header === null) {
return;
}
$notification = MaintenanceNotification::query()->with(['window', 'customer'])->find((int) $header->getBodyAsString());
if ($notification === null) {
return;
}
$notification->update(['sent_at' => now()]);
if ($notification->event === 'announcement' && $notification->window?->state === 'cancelled' && $notification->customer !== null) {
app(MaintenanceNotifier::class)->deliver(
$notification->window,
$notification->customer,
'cancelled',
new MaintenanceCancelledMail($notification->window, $notification->customer),
);
}
});
}
}