instance($run); $node = (string) $run->context('node'); $vmid = (int) $run->context('vmid'); $fqdn = $instance->subdomain.'.'.config('provisioning.dns.zone'); $pve = $this->pve->forHost($instance->host); $occ = 'cd /opt/nextcloud && docker compose exec -T app php occ '; // TLS + routing actually serving. if (! $instance->cert_ok || ! $this->traefik->certReachable($fqdn)) { return StepResult::fail('acceptance_failed:cert'); } // Nextcloud installed, not in maintenance, DB reachable. $status = $pve->guestExec($node, $vmid, $occ.'status --output=json'); $health = json_decode($status['out-data'] ?? '', true) ?: []; if ((int) ($status['exitcode'] ?? 1) !== 0 || ($health['installed'] ?? false) !== true || ($health['maintenance'] ?? false) !== false) { return StepResult::fail('acceptance_failed:nextcloud'); } // The admin account exists and is queryable. $admin = $pve->guestExec($node, $vmid, $occ.'user:info '.escapeshellarg((string) $instance->nc_admin_ref)); if ((int) ($admin['exitcode'] ?? 1) !== 0) { return StepResult::fail('acceptance_failed:admin'); } // A backup job is registered. if (! $this->hasResource($run, 'backup_job_id')) { return StepResult::fail('acceptance_failed:backup'); } // Monitoring target exists and reports healthy. $target = $instance->monitoringTargets()->first(); if ($target === null || ! $this->monitoring->isHealthy($target->external_id)) { return StepResult::fail('acceptance_failed:monitoring'); } return StepResult::advance(); } }