From d94cebae14f311f0e05c5613bc4890e712830059 Mon Sep 17 00:00:00 2001
From: "goodolclint-claude[bot]"
<323206664+goodolclint-claude[bot]@users.noreply.github.com>
Date: Tue, 1 Sep 2026 17:52:19 +0000
Subject: [PATCH] feat: VmService.RebootVm calls PVE's native reboot endpoint
---
src/PSProxmoxVE.Core/Services/VmService.cs | 36 ++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/src/PSProxmoxVE.Core/Services/VmService.cs b/src/PSProxmoxVE.Core/Services/VmService.cs
index 5b7764a..bc409e7 100644
--- a/src/PSProxmoxVE.Core/Services/VmService.cs
+++ b/src/PSProxmoxVE.Core/Services/VmService.cs
@@ -326,6 +326,42 @@ namespace PSProxmoxVE.Core.Services
}
}
+ ///
+ /// Reboots a VM through PVE's native reboot endpoint. Returns the task UPID.
+ ///
+ ///
+ /// PVE holds the guest's config lock across the whole shutdown and restarts the VM from
+ /// its own post-stop cleanup, so nothing can interleave between the two halves. Composing
+ /// a reboot client-side as shutdown + start instead races that cleanup: the start wins the
+ /// lock, cleanup then holds it for 30 s waiting on the newly started process, and the next
+ /// call fails with "can't lock file '/var/lock/qemu-server/lock-<vmid>.conf' - got timeout".
+ ///
+ /// The authenticated PVE session.
+ /// The cluster node name.
+ /// The VM ID.
+ /// Optional maximum seconds to wait for the shutdown half.
+ public PveTask RebootVm(PveSession session, string node, int vmid, int? timeoutSeconds = null)
+ {
+ if (session == null) throw new ArgumentNullException(nameof(session));
+ if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
+
+ var formData = new Dictionary();
+ if (timeoutSeconds.HasValue)
+ formData["timeout"] = timeoutSeconds.Value.ToString();
+
+ IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
+ try
+ {
+ var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/status/reboot", formData)
+ .GetAwaiter().GetResult();
+ return ParseTask(response, node);
+ }
+ finally
+ {
+ if (_injectedClient == null) client.Dispose();
+ }
+ }
+
/// Resets a VM (hard reset). Returns the task UPID.
/// The authenticated PVE session.
/// The cluster node name.