Skip to content

Feat/arch aarch64#354

Open
lausser wants to merge 12 commits into
hashicorp:mainfrom
lausser:feat/arch-aarch64
Open

Feat/arch aarch64#354
lausser wants to merge 12 commits into
hashicorp:mainfrom
lausser:feat/arch-aarch64

Conversation

@lausser

@lausser lausser commented May 14, 2026

Copy link
Copy Markdown
Contributor

Description

Add first-class aarch64 / ARM64 template support to the proxmox-iso and proxmox-clone builders via a new arch HCL field. With arch = "aarch64" and four companion fields, the plugin produces a working ARM64 template on Proxmox VE 9.1.9+ end-to-end.
There are no manual post-create fix-ups, no qemu_additional_args escape hatches, no additional_config map.
Inspiration came from this blog post: https://kwellkorn.de/2025/03/25/virtualisierung-von-arm-architektur-unter-proxmox/

What the arch field does:
arch is a create-only PVE setting; a post-create PUT silently fails and leaves the VM unbootable, so it must be in the initial VM-create payload.
The field accepts "" (host default), "x86_64", and "aarch64". The first two values are no-ops on existing builds.

Validation added for arch = "aarch64":

  • Requires bios = "ovmf" (AAVMF firmware; SeaBIOS is x86-only)
  • Requires efi_config block (UEFI boot disk)
  • Requires vga.type to be one of serial0serial3 (no VGA output on virt machine)
  • Requires at least one entry in serials when a serial VGA type is set

Arch-aware defaults (only when field is unset):

  • boot_iso.type"scsi" (aarch64 virt machine has no IDE controller)
  • additional_iso_files[*].type"scsi" (same reason)
  • cpu_type"cortex-a57" (PVE rejects x86 cpu models for aarch64)
  • machine"virt" (the correct QEMU machine type for ARM64)

arch = "x86_64" and arch = "" produce identical behavior to before this PR; this change is strictly additive.

Infrastructure commit (a5e4e73):
pinning github.com/Telmate/proxmox-api-go@f4e48ca (Telmate/proxmox-api-go#555, merged 2026-05-13) required absorbing 18 months of api-go API drift (context.Context plumbing, typed GuestID/GuestName, QemuNetworkInterfaces/QemuPciDevices restructure, pointer-wrapped EFI/RNG/Tags).
This commit is behavior-preserving for existing builds.

Two forced side-effects of the api-go bump reviewers should know about:

  1. Minimum Go lifted to 1.26.2 (cascades from api-go's own go.mod).
  2. The post-create EFI fix-up in the clone builder's step_start_vm.go is removed. The new api-go Create path handles it correctly, making the retry redundant.

Empirical validation (bum-proxmox1 / PVE 9.1.11, 2026-05-15):

  • x86_64 regression build: no errors
  • aarch64 build (default cpu_type = cortex-a57): (14m 28s on AMD Ryzen 9 Pro 8945HS); guest lscpu confirms Cortex-A57
  • aarch64 build (explicit cpu_type = cortex-a72): (14m 24s; PVE hardware tab and guest lacpu confirm correct CPU)

Resolved Issues

No open issue to close.

Rollback Plan

If a change needs to be reverted, we will roll out an update to the code within 7 days.

Changes to Security Controls

No changes to security controls. The new arch field affects only VM hardware configuration at create time; it does not touch access controls, encryption, or logging.

lausser added 5 commits May 14, 2026 20:02
…ons API

Pulls Telmate/proxmox-api-go forward from the Oct 2024 pin (b149708f) to
the merged tip at f4e48ca. The upstream library has restructured most
client methods to take context.Context, typed VM identifiers
(GuestID/GuestName), reshaped QemuNetworkInterfaces/QemuPciDevices, and
moved EFI/RNG/Tags handling to pointer-wrapped struct types. Every call
site and adjacent test mock is adjusted accordingly; behavior is
preserved.

One observable difference: the post-create SetVmConfig fix-up that
re-asserted EFIDisk on proxmox-clone builds is gone. The new Create
path handles the EFI disk reliably, and the explicit retry is
redundant.

api-go's own go.mod declares go 1.26.2; `go mod tidy` cascades that
floor into this module. Plugin CI will need to lift its Go matrix
accordingly.
…lues

Adds a new `arch` HCL field on the proxmox-iso and proxmox-clone builders
that selects the QEMU CPU architecture for the created VM template.
Accepted values: `""` (host default), `"x86_64"`, `"aarch64"`. Invalid
values are rejected at validate time before any API call is made.

This commit lands the field, the validation whitelist, and propagation
into the api-go CreateOptions on the initial POST. Companion validation
(bios/efi_config/vga/serials requirements when arch=aarch64) and
arch-aware defaults follow in the next two commits.
When arch=aarch64 is set, five companion settings must be configured
correctly for the build to reach a working PVE template. Each rule fires
at validate time, before any API call, naming the offending field by its
HCL name:

  - FR-004: arch=aarch64 + bios=seabios → error. aarch64 requires UEFI.
  - FR-009: arch=aarch64 + empty efi_config → error.
  - FR-010: arch=aarch64 + non-serial vga.type → error. QMP send-key for
    boot_command keystrokes only reaches the guest through a serial console.
  - FR-011: arch=aarch64 + serial vga.type + empty serials → error.
  - FR-012: arch=aarch64 + boot_iso.type="ide" → error. The QEMU virt
    machine type has no IDE bus.

Rules 2–5 live in builder/proxmox/common/config.go and run immediately
after the EFI normalization block (FR-009 reads the post-normalize
EFIStoragePool, so the position is load-bearing). Rule 6 lives in
builder/proxmox/iso/config.go because BootISO is on iso.Config.

Verbatim error strings are stable surface — see
specs/002-aarch64-proxmox-iso/contracts/feature-surface.md.
When arch=aarch64 and the corresponding companion field is unset, the
plugin substitutes an aarch64-appropriate default. Explicit user values
are always honored.

  - FR-006: boot_iso.type defaults to "scsi" (not "ide" — the QEMU virt
    machine type has no IDE bus). Index is intentionally left unset; SCSI
    auto-assignment finds a free slot at runtime.
  - FR-006: additional_iso_files[*].type defaults to "scsi" for the same
    reason.
  - FR-015: cpu_type defaults to "cortex-a57". PVE rejects "kvm64" (the
    plugin's x86 default) on the virt machine type with "CPU model 'kvm64'
    does not exist for configured vCPU architecture 'aarch64'".
  - FR-013: qemu_additional_args auto-injects "-device qemu-xhci -device
    usb-kbd" so boot_command keystrokes reach the guest. The virt machine
    type ships without a keyboard; QMP send-key is a silent no-op without
    one. Detection is strict empty-string equality — supplying any value
    fully disables the auto-inject. Device order is load-bearing.
Adds a Building aarch64 (ARM64) templates section to
docs/builders/iso.mdx that documents the prerequisites, the four hard
companion fields the plugin enforces when arch=aarch64, the arch-aware
defaults it substitutes, the qemu_additional_args opt-out gotcha, and a
worked HCL2 example that produces a Debian 13 ARM64 template on PVE 9.
@lausser

lausser commented May 14, 2026

Copy link
Copy Markdown
Contributor Author
Helium_20260515_004839 Helium_20260515_004643 Helium_20260515_004920

lausser added 4 commits May 15, 2026 00:55
Pinning Telmate/proxmox-api-go@f4e48ca in a5e4e73 cascades go 1.26.2
into our go.mod via go mod tidy (api-go's own go.mod floor). The CI
matrix reads .go-version for golangci-lint and the generate check; the
old 1.21.13 value causes a type-export format mismatch that breaks both
jobs. Bump to match.
Rerun make generate after adding the "Building aarch64 (ARM64) templates"
section to docs/builders/iso.mdx. Picks up the new arch field docs in the
compiled .web-docs/ tree.
golangci-lint v1.60.1 (the previous pinned version) bundles a Go
typechecker that cannot read export data produced by Go 1.26, causing
all typecheck-dependent linters to fail with "unsupported version: 2".
v2.x is the first release line with full Go 1.26 support; v1.x is
no longer maintained.

- go-validate.yml: bump golangci-lint-action v5 → v9.2.0, lint version
  v1.60.1 → v2.12.2
- .golangci.yml: migrate to v2 config format (version: "2",
  linters.default: none replaces disable-all, output.formats as array,
  remove linters.fast and run.skip-dirs-use-default which v2 dropped)
The previous v2 migration kept output sub-settings and linters-settings
that are not valid top-level keys in v2, causing exit code 3 (config
parse failure). Strip to the essential sections: linters, issues, run.
@lausser lausser force-pushed the feat/arch-aarch64 branch from 9b90186 to 68c1536 Compare May 14, 2026 23:33
lausser added 2 commits May 15, 2026 01:38
In v2 the exclusion settings moved out of the top-level issues section:
  issues.exclude-rules  → linters.exclusions.rules
  issues.exclude-files  → linters.exclusions.paths
The old keys cause a jsonschema validation error and prevent the linter
from starting at all.
…oimports is a formatter

In v2, gosimple was merged into staticcheck and goimports was
reclassified as a formatter. Update the config accordingly:
- remove gosimple from linters.enable (now covered by staticcheck)
- move goimports to formatters.enable with its own exclusions block
- update the gosimple S-code exclusion rule to use staticcheck
@lausser lausser marked this pull request as ready for review May 15, 2026 13:10
@lausser lausser requested a review from a team as a code owner May 15, 2026 13:10
Four corrections to the iso.mdx aarch64 worked example:

- boot_command: replace ISOLINUX boot: prompt sequence with the verified
  GRUB EFI edit-mode sequence (e, arrow navigation, F10). Debian arm64
  netinst uses GRUB EFI, not ISOLINUX; the previous sequence was silently
  swallowed by the GRUB menu and the build would hang until ssh_timeout.
  Verified empirically against Debian 13.4.0 arm64 netinst on PVE 9.1.11.
- boot_wait: 5s → 10s to give GRUB EFI time to render before keystrokes land.
- pre_enrolled_keys: true → false; Secure Boot key enrollment breaks unsigned
  or self-signed bootloaders in template builds.
- boot_iso: remove redundant iso_storage_pool (only meaningful with iso_url).

Regenerate .web-docs/ to match.
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