Commit Graph

357 Commits

Author SHA1 Message Date
goodolclint-claude[bot] 999bac6bdc ci: remove runner-side docker storage resources (moved to storage VM) 2026-08-31 19:56:10 +00:00
goodolclint-claude[bot] f4e5760080 ci: storage and answer services move to a VM inside the CI sandbox (2/2)
run-integration.sh: two-phase apply (storage VM configured before the
PVE guests boot), Pester PassThru with explicit failed/zero-test checks,
auto-ISO cache names embed the answer host, force-cleanup covers the
storage VM and imported cloud image, docker usage removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:56:02 +00:00
goodolclint-claude[bot] f48f0ea9ae ci: storage and answer services move to a VM inside the CI sandbox (1/2)
The nested PVE guests live on an isolated VLAN with no route to the
runner network (ADR 0032, homelab repo), and the runner-hosted Docker
services died with each ephemeral ARC pod anyway. A small cloud-image VM
in the ci pool now serves NFS, iSCSI, and the auto-install answer files
from inside the sandbox. Includes fixes from Codex and in-house review:
bpg provider >= 0.79.0, serial console for the resized cloud image,
key-based SSH (cloud images refuse password auth), CIDR validation,
storage VM in the headroom check, docker socket mounts removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:52:36 +00:00
GoodOlClint dc4d01c6fe Merge pull request #85 from GoodOlClint/ci/dynamic-node-selection
ci: provision picks the least-loaded node and guards host memory
2026-08-31 13:49:49 -05:00
goodolclint-claude[bot] 07b95aa4d0 Merge branch 'main' into ci/dynamic-node-selection 2026-08-31 18:44:58 +00:00
GoodOlClint 4e755fafb0 Merge pull request #86 from GoodOlClint/ci/claude-review-allowed-bots
ci: allow the goodolclint-claude and goodolclint-codex apps in Claude review
2026-08-31 13:44:20 -05:00
goodolclint-claude[bot] 71e12e68a1 ci: allow the goodolclint-claude and goodolclint-codex apps in Claude review
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 18:33:14 +00:00
goodolclint-claude[bot] 6bd1c37343 ci: PVE_TARGET_NODE is optional in the workflow docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 18:24:07 +00:00
goodolclint-claude[bot] d105f51246 ci: provision picks the least-loaded node and guards host memory
PVE_TARGET_NODE becomes optional: unset (or 'auto') queries
/cluster/resources for the online node with the most free memory and
refuses to provision when no node has vm_count x 8GiB + 8GiB headroom
free. The chosen node persists on the shared mount so cleanup and
force-cleanup aim at the node provision picked.

2026-08-31: a provision onto the pinned node OOM-killed a 16GiB
services-plane VM on the parent hypervisor; the pin predated a memory
bump that consumed the headroom it silently relied on. Needs PVEAuditor
on /nodes for the token's user (a non-privsep token ignores token ACLs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 18:23:23 +00:00
goodolclint-claude[bot] 6747878b19 Merge pull request #83 from GoodOlClint/dependabot/nuget/tests/PSProxmoxVE.Core.Tests/main/xunit.runner.visualstudio-4.0.0
Bump xunit.runner.visualstudio from 3.1.5 to 4.0.0
2026-08-31 17:22:31 +00:00
goodolclint-claude[bot] 3c97f0237a Merge branch 'main' into dependabot/nuget/tests/PSProxmoxVE.Core.Tests/main/xunit.runner.visualstudio-4.0.0 2026-08-31 17:19:58 +00:00
goodolclint-claude[bot] 105b2199d3 Merge pull request #82 from GoodOlClint/dependabot/nuget/tests/PSProxmoxVE.Core.Tests/main/Microsoft.NET.Test.Sdk-18.9.0
Bump Microsoft.NET.Test.Sdk from 18.5.1 to 18.9.0
2026-08-31 17:19:50 +00:00
goodolclint-claude[bot] 780efc8fbf Merge branch 'main' into dependabot/nuget/tests/PSProxmoxVE.Core.Tests/main/Microsoft.NET.Test.Sdk-18.9.0 2026-08-31 17:18:22 +00:00
goodolclint-claude[bot] e09ab64248 Merge pull request #81 from GoodOlClint/dependabot/nuget/src/PSProxmoxVE.Core/main/SharpCompress-0.50.4
Bump SharpCompress from 0.48.1 to 0.50.4
2026-08-31 17:18:17 +00:00
goodolclint-claude[bot] b16ea4164c Merge branch 'main' into dependabot/nuget/src/PSProxmoxVE.Core/main/SharpCompress-0.50.4 2026-08-31 17:17:36 +00:00
goodolclint-claude[bot] 1c6eb6919c Merge pull request #77 from GoodOlClint/dependabot/github_actions/main/actions/setup-dotnet-6
chore(deps): bump actions/setup-dotnet from 5 to 6
2026-08-31 17:17:28 +00:00
goodolclint-claude[bot] 60e84712f3 Merge branch 'main' into dependabot/github_actions/main/actions/setup-dotnet-6 2026-08-31 17:15:49 +00:00
goodolclint-claude[bot] 02595ca8e6 Merge pull request #73 from GoodOlClint/dependabot/github_actions/main/actions/checkout-7
chore(deps): bump actions/checkout from 6 to 7
2026-08-31 17:15:32 +00:00
dependabot[bot] aae3764227 Bump xunit.runner.visualstudio from 3.1.5 to 4.0.0
---
updated-dependencies:
- dependency-name: xunit.runner.visualstudio
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 17:10:23 +00:00
dependabot[bot] eacd570922 Bump Microsoft.NET.Test.Sdk from 18.5.1 to 18.9.0
---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-version: 18.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 17:10:15 +00:00
dependabot[bot] fea29874b2 Bump SharpCompress from 0.48.1 to 0.50.4
---
updated-dependencies:
- dependency-name: SharpCompress
  dependency-version: 0.50.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 17:09:36 +00:00
dependabot[bot] 9fdd89fd95 chore(deps): bump actions/setup-dotnet from 5 to 6
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5 to 6.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 17:08:15 +00:00
dependabot[bot] 93c23d21a4 chore(deps): bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 17:07:37 +00:00
GoodOlClint cb49507168 Merge pull request #84 from GoodOlClint/ci/pool-ci
ci: nested PVE guests land in the ci pool with VMIDs 5081-5092
2026-08-23 10:01:56 -05:00
Clint Branham 2897feb43e ci: cleanup honours PVE_TARGET_NODE and the ISO storage var
A pool-scoped token cannot list /nodes, so node discovery fell back to 'pve'
and force-cleanup silently found no VMs to destroy.
2026-08-23 09:27:27 -05:00
Clint Branham 5b896b4681 ci: integration jobs run on the psproxmoxve ARC scale set
ARC scale sets match runs-on by scale-set name, not by label list.
2026-08-23 09:08:54 -05:00
Clint Branham d3f05187dd ci: nested PVE guests land in the ci pool with VMIDs 5081-5092
The runner token is pool-scoped (PVEVMAdmin on /pool/ci only), so VM.Allocate
succeeds only with pool_id set; VMIDs move into the 5000-5999 CI range.
2026-08-23 09:06:11 -05:00
Clint Branham c7eff18899 ci: feed disk/iso storage and bridge from repo vars for rebuilt homelab 2026-08-23 02:29:49 -05:00
GoodOlClint 3a50dd2fb7 Merge pull request #70 from GoodOlClint/chore/release-0.2.0
chore: release 0.2.0
v0.2.0
2026-05-22 15:50:49 -05:00
Clint Branham ee69c699b1 chore: release 0.2.0
Minor bump: this release adds features (New-PveVm disk controller/IO
options and Get-PveVmConfig key surfacing, #65) alongside two bug fixes
(#64 semicolon form-encoding, #68 guest-exec argv).

Updates the three release artifacts in lockstep: psd1 ModuleVersion,
psd1 ReleaseNotes, and CHANGELOG ([0.2.0] cut from [Unreleased]).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 15:47:36 -05:00
GoodOlClint 5cb5c90db8 Merge pull request #69 from GoodOlClint/fix/guest-exec-args
fix: deliver Invoke-PveVmGuestExec -Args as argv (#68)
2026-05-22 14:41:45 -05:00
Clint Branham 56dcf22cea fix: address PR #69 review feedback
- VmService.ExecuteGuestCommand: guard against null elements in -Args.
  The old JSON-serialization tolerated nulls (as "null"); the repeated-key
  path would NRE in EncodeFormValue. Throw a clear ArgumentException instead.
- findings.json: refresh the stale counters block (untouched since F085) to
  the actual ledger state — next_id 92, resolved 83 — and bump last_updated
  to 2026-05-22. last_scan_date stays 2026-03-26 (F086–F091 came from issue
  triage, not a formal review scan).
- VmServiceTests: add empty-array (single command entry) and null-element
  (throws) cases.

Note: F091 is the correct next ID — F086–F090 already exist from prior
merged PRs (#60/#61/#66/#67); only the counters were lagging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:38:54 -05:00
Clint Branham bc71ed4a12 fix: deliver Invoke-PveVmGuestExec -Args to the guest as argv
ExecuteGuestCommand JSON-serialized the args array into the agent/exec
'input-data' field — which is the process's STDIN, not its arguments. So
guest commands ran with no argv: cmd.exe started interactively and the
JSON blob ['/c','echo',...] arrived at its prompt.

PVE's agent/exec 'command' parameter is itself an array (element 0 = the
executable, the rest = argv) sent as repeated form keys. The low-level
client couldn't express repeated keys (Dictionary<string,string> only),
so:

- Add PostAsync(string, IEnumerable<KeyValuePair<string,string>>) to
  IPveHttpClient/PveHttpClient; BuildFormContent now emits one key=value
  field per pair, so a key may repeat.
- ExecuteGuestCommand builds command = [exe] + args as repeated 'command'
  fields and no longer touches input-data.

Tests: form-encoder repeated-key + per-value encoding cases; VmService
tests asserting the command array, order, and absence of input-data; an
integration regression guard that echoes an arg and checks it round-trips
as stdout.

Tracked as F091. Closes #68.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:32:04 -05:00
GoodOlClint 5266accfae Merge pull request #67 from GoodOlClint/feat/new-pvevm-disk-options
feat: New-PveVm disk controller/IO options + surface all Get-PveVmConfig keys (#65)
2026-05-22 14:27:30 -05:00
Clint Branham e959fbb9b8 fix: correct New-PveVm disk-options warning text
Now that -ScsiHardware is part of HasDiskOptions(), the old "options were
ignored" wording was misleading: scsihw is written unconditionally as a
VM-level key and is never ignored. Reword to state that the per-disk
options are the ones dropped, and that -ScsiHardware (if specified) is
still applied. Accurate whether or not -ScsiHardware was passed.

Addresses PR #67 follow-up review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:25:21 -05:00
Clint Branham 80b70cdaf6 fix: address PR #67 review feedback
- HasDiskOptions(): include -ScsiHardware so the "disk options ignored"
  warning fires when -ScsiHardware is passed without -DiskStorage/-DiskSize.
- PveVmConfig.AdditionalProperties: lazy-init a backing field so the native
  dictionary is built once rather than reallocated on every property access
  (matters when iterating many configs in a pipeline). Safe because the model
  is effectively immutable after deserialization.
- New-PveVm.Tests.ps1: add a case asserting -DiskIoThread on scsi with a
  wrong -ScsiHardware (virtio-scsi-pci) is rejected, covering the validator's
  "!= virtio-scsi-single" branch (not just the null case).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:18:32 -05:00
Clint Branham 63ee16a9e7 Merge remote-tracking branch 'origin/main' into feat/new-pvevm-disk-options
# Conflicts:
#	docs/review/findings.json
2026-05-22 14:15:20 -05:00
GoodOlClint dd5739046e Merge pull request #66 from GoodOlClint/fix/form-encode-semicolon
fix: percent-encode ';' in form values (#64)
2026-05-22 14:13:03 -05:00
Clint Branham c1714048b4 feat: disk controller/IO options on New-PveVm + surface all Get-PveVmConfig keys
New-PveVm (F089):
- Add -DiskBus (virtio/scsi/sata/ide, default virtio), -ScsiHardware (scsihw),
  -DiskIoThread, -DiskAio, -DiskSsd, -DiskDiscard, -DiskCache so a tuned disk
  (e.g. virtio-scsi-single + scsi0,iothread=1,aio=native,ssd=1,discard=on) can
  be created in one call instead of diskless + a hand-built Set-PveVmConfig string.
- Disk spec built via BuildDiskSpec; ValidateDiskOptions runs before ShouldProcess
  and rejects ssd on virtio and iothread on sata/ide or scsi-without-virtio-scsi-single
  with clear errors, instead of letting PVE fail at VM start.

Get-PveVmConfig (F090):
- PveVmConfig was a fixed allow-list, silently dropping keys like scsihw, efidisk0,
  tpmstate0, hostpci0. Add typed scsihw/efidisk0/tpmstate0 plus a [JsonExtensionData]
  catch-all exposed as AdditionalProperties (native types via JsonHelper.ToNative,
  per D013 — no JToken leakage). Makes the disk tuning above verifiable by reading
  the config back.

Closes #65.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:10:14 -05:00
Clint Branham 3fd770d84c fix: percent-encode ';' in form values
PveHttpClient.EncodeFormValue encoded &, =, +, space, and % but left ';'
literal. PVE's application/x-www-form-urlencoded parser treats a raw ';'
as a field separator (the historical alternative to '&'), so a value like

    boot=order=scsi0;ide2

was split into 'boot=order=scsi0' plus an empty 'ide2' field, and PVE
rejected the PUT with "ide2: unable to parse drive options". This broke
any multi-device boot order set via Set-PveVmConfig -AdditionalConfig,
and any other value containing ';'.

Encode ';' as %3B. Safe under the existing minimal-encoding policy that
keeps ':' and '!' literal for cluster-join: cluster-join payloads never
contain ';', and PVE url-decodes config form values.

Tracked as F088. Closes #64.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:04:31 -05:00
GoodOlClint 5e5a8fcda4 Merge pull request #63 from GoodOlClint/docs/changelog-and-releasenotes-catchup
docs: cut CHANGELOG into versioned entries, refresh ReleaseNotes
v0.1.3
2026-05-20 19:27:05 -05:00
Clint Branham 79c97ec211 docs: cut CHANGELOG into versioned entries, refresh ReleaseNotes
Doc hygiene catch-up surfaced by the PR #62 review:

- CHANGELOG.md: the [Unreleased] section had accumulated all post-preview
  work without ever being cut into release entries. Promote it into:
    - [0.1.3] - new fixes from #58 (DiskSize normalization) and #59
      (HttpClient -TimeoutSeconds + RequestTimeout surfacing)
    - [0.1.2] - #43/#44/#45 fixes from PR #46
    - [0.1.1] - the cmdlet expansion + OpenAPI validation that
      actually shipped to PSGallery as 0.1.1
  Reset [Unreleased] to empty.

- src/PSProxmoxVE/PSProxmoxVE.psd1: replace the stale "Initial preview
  release" ReleaseNotes (carried over since 0.1.0-preview) with actual
  0.1.3 notes. PSGallery shows this on the version page.

- CLAUDE.md: document the release process so future bumps update the
  psd1 version, psd1 ReleaseNotes, and CHANGELOG together before the
  tag is cut. Prevents this hygiene gap from recurring.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 19:23:29 -05:00
GoodOlClint 098ea7e8d4 Merge pull request #62 from GoodOlClint/chore/bump-version-0.1.3
chore: bump version to 0.1.3
2026-05-20 19:19:01 -05:00
Clint Branham ff728fc6d6 chore: bump version to 0.1.3
Releases #58 (LVM disk-size unit normalization) and #59 (HttpClient
timeout / -TimeoutSeconds) fixes to PSGallery.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 19:14:54 -05:00
GoodOlClint 02254ed13b Merge pull request #61 from GoodOlClint/fix/http-client-timeout
fix: add -TimeoutSeconds for long-running HTTP calls (#59)
2026-05-20 18:26:08 -05:00
Clint Branham cb97a86c9f fix: drop net48-incompatible TimeoutException filter in timeout catch
The when filter (ex.InnerException is TimeoutException) only matches on
.NET 5+. On .NET Framework 4.8 — which CI exercises via the test project's
net48 target — HttpClient.Timeout throws a bare TaskCanceledException
with no inner exception, so CI failed:

  Expected: typeof(PSProxmoxVE.Core.Exceptions.PveApiException)
  Actual:   typeof(System.Threading.Tasks.TaskCanceledException)
  ---- System.Threading.Tasks.TaskCanceledException : A task was canceled.

PveHttpClient.SendAsync never passes a CancellationToken to the inner
HttpClient.SendAsync, so the only way a TaskCanceledException can reach
this catch is HttpClient.Timeout firing — true on net48, .NET Core, and
.NET 5+. Drop the filter and wrap unconditionally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:22:47 -05:00
Clint Branham a1d9550d83 fix: surface HttpClient timeouts as PveApiException(RequestTimeout)
When HttpClient.Timeout elapses, .NET throws TaskCanceledException — not
HttpRequestException — so the existing catch in PveHttpClient.SendAsync
missed it and callers got a raw stack trace. With -TimeoutSeconds now
configurable and documented, this gap became user-visible.

In .NET 5+ HttpClient surfaces transport timeouts as TaskCanceledException
with a TimeoutException inner; user-driven token cancellation does not.
Catch by that inner-type signature and rethrow as PveApiException with
HttpStatusCode.RequestTimeout, the resource path, and a message that
reports the configured timeout.

Adds SendAsync_TimeoutFires_ThrowsPveApiExceptionWithRequestTimeout which
swaps in a delaying HttpMessageHandler with a 50ms timeout to exercise
the path deterministically. Drops the redundant
DefaultSessionTimeoutIs100Seconds test (covered by
PveSessionTests.Timeout_DefaultIs100Seconds and the existing flow-through
test).

Addresses PR #61 review feedback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:17:31 -05:00
Clint Branham ea2bcdc336 Merge main into fix/http-client-timeout
Resolves findings.json conflict — F086 (from #60, merged into main) and
F087 (this branch) both append to the trailing findings array. Kept both
entries, in numeric order.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:17:20 -05:00
GoodOlClint 239a6b8fe9 Merge pull request #60 from GoodOlClint/fix/disk-size-unit-normalization
fix: normalize -DiskSize/-RootFsSize before sending to PVE (#58)
2026-05-20 18:10:36 -05:00
Clint Branham f3b06171b2 fix: add -TimeoutSeconds for long-running HTTP calls
PveHttpClient was constructed without setting HttpClient.Timeout, so
.NET's 100s default applied to every request. Multi-GB ISO uploads via
Send-PveFile on a real LAN reliably tripped this with TaskCanceledException
after 100 seconds, and there was no way to override it.

- PveSession gains a Timeout (TimeSpan) property, defaulting to 100s.
- PveHttpClient accepts an optional per-instance timeout override that
  takes precedence over the session timeout.
- Connect-PveServer exposes -TimeoutSeconds to set the session default.
- Send-PveFile and Invoke-PveStorageDownload expose -TimeoutSeconds with
  a 30-minute implicit default so large uploads/downloads do not trip
  the 100s default. -TimeoutSeconds 0 means Timeout.InfiniteTimeSpan.

Tracked as F087. Closes #59.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:08:26 -05:00