instance($run); $node = (string) $run->context('node'); $plan = $this->plan($run); $templateVmid = (int) ($plan['template_vmid'] ?? 0); if ($templateVmid === 0) { return StepResult::fail('template_missing'); } $pve = $this->pve->forHost($instance->host); $name = 'nc-'.$instance->subdomain; // Reserve a deterministic vmid + breadcrumb BEFORE the clone call, so a // crash never leads a retry to mint a NEW vmid (orphaning the first clone). if (! $this->hasResource($run, 'vmid')) { $vmid = $pve->nextVmid(); $instance->update(['vmid' => $vmid, 'status' => 'provisioning']); $run->mergeContext(['vmid' => $vmid]); $this->recordResource($run, $instance->host, 'vmid', (string) $vmid); } $vmid = (int) $run->context('vmid'); // Clone once, keyed to the reserved vmid; record the task ref immediately. $upid = $run->context('clone_upid'); if ($upid === null) { // Crash-recovery: if the VM already exists, a prior attempt cloned it // (but lost the task ref) — don't re-clone the same vmid (Proxmox rejects). if ($pve->vmExists($node, $vmid)) { return StepResult::advance(); } $upid = $pve->cloneVm($node, $templateVmid, $vmid, $name); $run->mergeContext(['clone_upid' => $upid]); } return $this->pollClone($pve, $node, (string) $upid); } private function pollClone(ProxmoxClient $pve, string $node, string $upid): StepResult { $task = $pve->taskStatus($node, $upid); if (($task['status'] ?? '') === 'running') { return StepResult::poll(10, 'cloning template'); } if (($task['exitstatus'] ?? 'OK') !== 'OK') { return StepResult::fail('clone_failed: '.($task['exitstatus'] ?? 'unknown')); } return StepResult::advance(); } }