instance($run); if ($instance === null) { return StepResult::fail('no_instance'); } // Every package, the same nightly window — see the class docblock for // why this is read from one place rather than derived from the plan. $schedule = '02:00'; if (! $instance->backups()->exists()) { $jobId = $this->pve->forHost($instance->host) ->createBackupJob((string) $run->context('node'), (int) $instance->vmid, $schedule); // Local row before anything else can fail, exactly as RegisterBackup // does it, so a retry finds the job rather than creating a second. $instance->backups()->firstOrCreate( ['external_job_id' => $jobId], ['schedule' => $schedule, 'status' => 'scheduled'], ); } $this->settleMonitoring($run, $instance); return StepResult::advance(); } /** * Monitoring is observability, not the product. * * The same judgement RegisterMonitoring makes during a build applies here * with more force: a customer's plan change must not fail because Kuma is * down. The gap is recorded on the run and the change goes through. */ private function settleMonitoring(ProvisioningRun $run, Instance $instance): void { $url = 'https://'.$instance->subdomain.'.'.ProvisioningSettings::dnsZone().'/status.php'; if ($instance->monitoringTargets()->where('url', $url)->exists()) { return; } try { $targetId = $this->monitoring->registerTarget($instance->subdomain, $url); } catch (Throwable $e) { $run->events()->create([ 'step' => $this->key(), 'attempt' => $run->attempt, 'outcome' => 'info', 'message' => 'Monitoring übersprungen (Dienst nicht erreichbar): '.$e->getMessage(), ]); return; } $instance->monitoringTargets()->firstOrCreate( ['external_id' => $targetId], // 'unknown', not 'up': registering a check is not passing one. ['url' => $url, 'status' => 'unknown'], ); } }