Modifies uploaded ISO's GRUB config in-place to set timeout=0 and inject
proxmox-start-auto-installer + answer-url kernel params, enabling fully
hands-off installation. Adds /api/boot/auto-answer endpoint that identifies
hosts by ARP-resolving the requester's IP to MAC address.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CPIO-embedded initrd approach failed (too large for iPXE memory).
Instead, use iPXE's sanhook to connect the ISO as a SAN block device
before booting with kernel/initrd. The installer finds the ISO on the
SAN device while we retain control over kernel parameters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of sanboot (which can't pass kernel params for automation),
switch back to kernel/initrd boot. The ISO is embedded in the initrd
as a CPIO append. A pxe-init wrapper script loop-mounts the ISO
before handing off to the original init, so the installer finds it
as a block device. Uses rdinit=/pxe-init kernel parameter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Proxmox has no kernel parameter for HTTP ISO fetch. Instead, use
iPXE's sanboot command to present the ISO as a virtual CD over HTTP.
The installer finds it as a block device and boots normally.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Proxmox installer needs the full ISO to access packages and
installer data. Previously the ISO was deleted after extracting
kernel+initrd. Now we keep it as original.iso and serve it via HTTP.
The iPXE script passes proxmox-iso-url=<url> so the installer
fetches the ISO over the network instead of scanning block devices.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
iPXE needs --name on the initrd command and initrd=<name> on the
kernel line to properly pass the initrd to the kernel. Without this,
the kernel never receives the initrd, causing VFS mount failure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Proxmox initrd is too large for the default ramdisk allocation,
causing VFS to fail mounting root. Add ramdisk_size=16777216 (16GB)
along with rw, quiet, and splash=verbose for proper installer boot.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The HostnamePrefix field on ServerType was loaded from YAML but never used —
hostnames are user-provided. This removes the field and adds explicit
duplicate checks (hostname + MAC) with clear per-field error messages in
both the JSON API and web UI, backed by a new GetByHostname store method
with case-insensitive matching.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Operations are now clickable from the host detail page, linking to
/ops/{id} which shows the operation info, host link, duration, and
activity log filtered to that operation. Active operations can be
cancelled, which transitions the host to failed and releases the lock.
SSE activity events now include operation_id for real-time filtering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
iPXE was stuck in a loop: boot iPXE -> DHCP -> get ipxe.0 again ->
boot iPXE -> repeat. Add tag:!ipxe to pxe-service directives so
iPXE clients get the HTTP script URL via dhcp-boot instead of being
served the bootloader again.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hosts stuck in states like pxe_ready had zero visibility into why.
This adds a persistent activity log that records every meaningful
step (state transitions, PXE events, cluster join stages, failures)
and surfaces it on the host detail page with live SSE updates.
Includes a stuck-detection warning banner when a host sits in
pxe_ready for >10 minutes with no iPXE request.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dhcp-boot alone does not send PXE vendor extensions (option 43) that
PXE clients need in proxy DHCP mode. Switch to pxe-service directives
for initial PXE boot, keep dhcp-boot only for iPXE chainloading.
Create .0 symlinks for pxe-service filename convention. Restore
dhcp-ignore=tag:!known filtering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dnsmasq sees PXE requests but never responds. Remove the known-host
filter to determine if tag matching is the issue or if the problem
is elsewhere in the proxy DHCP flow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bare MACs in dhcp-hostsfile were not auto-setting the known tag in
proxy DHCP mode, causing dhcp-ignore=tag:!known to drop all requests.
Explicitly write set:known per host entry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove tag:known filter from dhcp-range — in proxy DHCP mode the tag
filter prevents responses. dhcp-ignore=tag:!known still filters
unknown hosts. Also copy ipxe.efi and undionly.kpxe from the system
ipxe package into the TFTP root at startup so clients can actually
download the bootloader.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dnsmasq exited with status 3 because the tftp-root directory didn't
exist at startup. Also replaced hardcoded 192.168.1.0 in dhcp-range
with the configured subnet value.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace prototype dark theme with a professional light-theme design
using Outfit (UI) and IBM Plex Mono (data) fonts, navy topbar, white
card surfaces, and a full CSS variable system for colors, shadows,
spacing, and radii. Add LED status indicators, panel components,
and structured tile layout with header/meta/footer sections.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ISO uploads now show a progress bar during file transfer (via XHR
upload.onprogress) and real-time extraction status (via SSE events
through the existing Hub). Falls back to plain form POST if JS is
disabled.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Upload Proxmox ISOs via API or dashboard UI, extract kernel+initrd
using pure-Go iso9660 library, store on disk, and serve over HTTP
for PXE booting. Dynamic kernel/initrd filenames per image replace
the previous hardcoded paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The //go:embed static directive nests files under static/, so after
StripPrefix removes /static/ from the URL, the FileServer couldn't find
the files. Use fs.Sub to root the FS at the static/ subdirectory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove /etc/provisioning/keys mount (ephemeral keys are in-memory now)
- Remove /etc/provisioning VOLUME from Dockerfile
- Add deploy/install.sh that creates config files before docker compose up,
preventing Docker from creating directories in place of missing bind mounts
- Update README with install script usage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Gitea's cache server is unreachable, causing setup-go to block on a
failed cache restore. Disable it since the Docker build layer caches
dependencies independently.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The pattern `provisioning` matched both the binary and the directory.
Use `/provisioning` to only match at the repo root.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generate a fresh ed25519 key pair at rebuild time, inject the public key
into the Proxmox answer file, use the private key for cluster join over
SSH, then remove the key from both the remote host and the database.
This eliminates the need to manage static SSH keys in config/secrets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Gitea Actions workflow: test → build → push to container registry
- docker-compose.yml for host deployment (host network for PXE)
- Update example config to use container paths (/data, /etc/provisioning)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>