Skip to content

feat(proxmox): cloud-init packages + runcmd via per-VM vendor snippet (E1)#145

Merged
astrojerms merged 1 commit into
mainfrom
feat/e1-cloudinit-packages
Jul 19, 2026
Merged

feat(proxmox): cloud-init packages + runcmd via per-VM vendor snippet (E1)#145
astrojerms merged 1 commit into
mainfrom
feat/e1-cloudinit-packages

Conversation

@astrojerms

Copy link
Copy Markdown
Owner

E1 — cloud-init packages + runcmd

Extends the Proxmox VirtualMachine cloudInit spec so a VM can install host packages and run first-boot commands — the one true prerequisite for F1 (Longhorn), which needs open-iscsi on nodes.

Design decision: per-VM vendor snippet

Proxmox has no native ci* option for arbitrary packages/runcmd. So these render into a real #cloud-config snippet:

  • client.RenderVendorData(packages, runcmd) builds the config via yaml.Marshal (arbitrary commands escape safely — no hand-concatenation), always leading runcmd with the qemu-guest-agent enablement, then the user's commands. package_update: true is emitted whenever packages are present.
  • Uploaded to snippets storage as a per-VM file (openctl-vendor-<vmid>.yaml) and attached via cicustom vendor=.
  • Why vendor= not user=: vendor-data is additive to Proxmox's generated user-data, so ciuser/cipassword/sshkeys are preserved. A user= snippet would replace them.

Risk containment

The no-packages/no-runcmd path is byte-identical to before — it still uses the shared static qemu-agent snippet, so the metal-validated QGA IP-discovery flow is untouched. Only when packages/runcmd are set does the code switch to the per-VM combined snippet (which folds the agent commands in, so IP discovery still works). Both create paths (template clone, cloud-image) now share one applyCloudInitVendorSnippet helper.

Also

  • Documents the previously-missing packageUpgrade field in the CUE schema.
  • plugins/proxmox gains a yaml.v3 indirect require + a new go.sum (transitive via pkg/proxmox/client).

Tests

  • RenderVendorData: ordering (agent-first), omitempty for no-packages, special-char round-trip (colons/quotes/hashes in runcmd parse back intact).
  • ParseVMSpec: packages/runcmd parsed, non-string entries skipped.
  • Handler wiring: full create path asserts the per-VM snippet upload (name + content) and the cicustom=vendor=local:snippets/openctl-vendor-200.yaml config.

Local CI: build, go test ./... (+-race on proxmox), gofmt, vet, staticcheck, golangci-lint (0 issues), modernize — root and the proxmox plugin module — all green.

Metal validation (does open-iscsi actually install on a real node) is a homelab follow-up, consistent with how A1–A4 shipped.

… (E1)

Extend the VirtualMachine cloudInit spec with `packages` (installed on
first boot, index refreshed first) and `runcmd` (first-boot commands),
so a VM can install node prerequisites like open-iscsi for Longhorn.

Proxmox has no native ci* option for arbitrary packages/runcmd, so these
render into a per-VM cloud-init vendor snippet (client.RenderVendorData,
via yaml.Marshal for safe escaping of arbitrary commands) uploaded to
snippets storage and attached with `cicustom vendor=`. vendor-data is
additive to Proxmox's generated user-data — unlike a `user=` snippet,
which would replace ciuser/cipassword/sshkeys — so those are preserved.

The no-packages/no-runcmd path keeps the shared static qemu-agent snippet
byte-identical (no risk to the metal-validated QGA IP-discovery flow); the
combined path folds the agent runcmd in so IP discovery still works. Both
proxmox create paths (template clone, cloud-image) share one helper.

Also documents the previously-missing `packageUpgrade` in the CUE schema.
plugins/proxmox gains a yaml.v3 indirect require + go.sum (transitive via
pkg/proxmox/client).

Unblocks F1 (Longhorn). Tests: vendor-data render (ordering, omitempty,
special-char round-trip), spec parse, handler wiring (per-VM upload +
cicustom). Metal validation of actual package install is a homelab
follow-up, like A1-A4.
@astrojerms
astrojerms merged commit 1fc6c29 into main Jul 19, 2026
6 checks passed
@astrojerms
astrojerms deleted the feat/e1-cloudinit-packages branch July 19, 2026 23:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant