host($run); // Idempotent: token already minted and persisted. if ($this->hasResource($run, 'pve_token') && filled($host->api_token_ref)) { return StepResult::advance(); } $role = (string) config('provisioning.proxmox.role_id'); $privs = (string) config('provisioning.proxmox.role_privs'); $user = (string) config('provisioning.proxmox.user'); $tokenName = (string) config('provisioning.proxmox.token_name'); $this->keyLogin($this->shell, $host); // Role / user / ACL are idempotent (ignore "already exists"). $this->shell->run('pveum role add '.escapeshellarg($role).' -privs '.escapeshellarg($privs).' || true'); $this->shell->run('pveum user add '.escapeshellarg($user).' || true'); $this->shell->run('pveum acl modify / -user '.escapeshellarg($user).' -role '.escapeshellarg($role).' || true'); // Drop any half-created token from a prior crashed attempt, then mint fresh. $this->shell->run('pveum user token remove '.escapeshellarg($user).' '.escapeshellarg($tokenName).' || true'); $result = $this->shell->run( 'pveum user token add '.escapeshellarg($user).' '.escapeshellarg($tokenName).' -privsep 0 --output-format json' ); if (! $result->ok()) { return StepResult::retry(20, 'Proxmox token creation failed'); } $data = json_decode($result->stdout, true); $secret = $data['value'] ?? null; if (blank($secret)) { return StepResult::retry(20, 'Proxmox token secret was empty'); } $tokenId = $data['full-tokenid'] ?? "{$user}!{$tokenName}"; $host->update(['api_token_ref' => $tokenId.'='.$secret]); $this->recordResource($run, $host, 'pve_token', $tokenId); return StepResult::advance(); } }