Initial commit: MAAS-deployable Proxmox VE images with cluster automation

Builds a Proxmox VE image that MAAS can deploy to bare metal, plus first-boot
automation that configures the node and joins it to a Proxmox cluster with no
manual steps.

The image starts from the official Debian cloud image and installs proxmox-ve
on top of it, rather than capturing a raw disk from the Proxmox ISO. That keeps
MAAS in control of partitioning, networking, SSH keys and cloud-init, and makes
moving between Proxmox releases a variable change instead of a rewrite.

Contents:

  * Makefile driving the whole flow: build, verify, preseed, upload
  * customize-proxmox.sh, run inside the Packer build VM, which layers Proxmox
    onto the Debian cloud image and resets the pmxcfs node identity so one image
    can produce many nodes
  * pve-maas-init, a first-boot state machine covering /etc/hosts, node-unique
    identifiers, the root password, vmbr0 conversion, cluster create/join and
    the local-lvm thin pool; each stage is resumable across reboots
  * curtin-hooks, which stops curtin installing a kernel over APT and pins
    interface names by MAC so they match what MAAS recorded at commissioning
  * a MAAS curtin preseed template and cloud-init examples
  * deploy-cluster.sh, which builds a whole cluster through the MAAS API
  * verify-image.sh, 22 static checks on the produced tarball

Cluster identity lives entirely in deploy-time cloud-init user-data, so a single
image and preseed can build any number of independent clusters.

Verified end to end against MAAS 3.7.2: proxmox-ve 9.2.0 / pve-manager 9.2.11 /
kernel 7.0.14-15-pve, deployed to two machines that formed a quorate cluster with
local-lvm on both, with no manual intervention.

The README documents four failure modes found along the way that all fail
silently: curtin rejecting "kernel: null", pvenetcommit overwriting the network
configuration at boot, interface renaming leaving the link down, and a systemd
ordering cycle that made systemd delete the service's start job.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-04 21:50:17 +02:00
commit 3d4841f31c
18 changed files with 3395 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
#cloud-config
# Kumenin ILK dugumu: yeni bir kume olusturur.
#
# MAAS ile deploy:
# maas $PROFILE machine deploy $SYSTEM_ID \
# osystem=custom distro_series=proxmox-ve-9 \
# user_data="$(base64 -w0 01-first-node.yaml)"
write_files:
- path: /etc/pve-maas/conf.d/50-pve.conf
permissions: "0600"
owner: root:root
content: |
# root@pam parolasi - web arayuzu ve diger dugumlerin katilimi icin sart.
# Hash uretmek icin: openssl passwd -6
PVE_ROOT_PASSWORD_HASH='$6$DEGISTIRIN$REPLACE.WITH.YOUR.OWN.HASH'
PVE_CLUSTER_MODE=create
PVE_CLUSTER_NAME=pve-cluster-01
# Corosync icin ayri bir ag kullaniyorsaniz:
# PVE_CLUSTER_LINK0=198.51.100.11
PVE_NET_APPLY=reboot
PVE_THINPOOL=auto

View File

@@ -0,0 +1,31 @@
#cloud-config
# Mevcut bir kumeye katilan dugum.
write_files:
- path: /etc/pve-maas/conf.d/50-pve.conf
permissions: "0600"
owner: root:root
content: |
PVE_ROOT_PASSWORD_HASH='$6$DEGISTIRIN$REPLACE.WITH.YOUR.OWN.HASH'
PVE_CLUSTER_MODE=join
# Kumede zaten bulunan bir dugumun IP'si
PVE_CLUSTER_PEER=192.0.2.11
# O dugumun root@pam parolasi (duz metin olmak zorunda - API dogrulamasi icin)
PVE_CLUSTER_PEER_PASSWORD='degistirin'
# Onerilen: parmak izini onceden verin (TOFU riskini kaldirir)
# openssl s_client -connect 192.0.2.11:8006 </dev/null 2>/dev/null | \
# openssl x509 -noout -fingerprint -sha256 | cut -d= -f2
# PVE_CLUSTER_FINGERPRINT='AA:BB:CC:...'
PVE_CLUSTER_FINGERPRINT_DISCOVER=true
# PVE_CLUSTER_LINK0=198.51.100.12
# Katilimdan sonra bu dosyadaki parolalar silinir
PVE_CLUSTER_WIPE_SECRETS=true
PVE_NET_APPLY=reboot
PVE_THINPOOL=auto

View File

@@ -0,0 +1,17 @@
#cloud-config
# Tek basina (kumesiz) Proxmox VE dugumu.
write_files:
- path: /etc/pve-maas/conf.d/50-pve.conf
permissions: "0600"
owner: root:root
content: |
PVE_ROOT_PASSWORD_HASH='$6$DEGISTIRIN$REPLACE.WITH.YOUR.OWN.HASH'
PVE_CLUSTER_MODE=none
# VLAN farkindalikli kopru isteniyorsa:
# PVE_NET_VLAN_AWARE=true
# Ikinci diski VM depolamasi olarak kullan:
# PVE_THINPOOL_DISK=/dev/sdb
# PVE_THINPOOL_VG=pve

View File

@@ -0,0 +1,49 @@
#cloud-config
# Karmasik ag: bond + VLAN. Otomatik vmbr0 donusumunu kapatip
# /etc/network/interfaces dosyasini kendimiz yaziyoruz.
write_files:
- path: /etc/pve-maas/conf.d/50-pve.conf
permissions: "0600"
owner: root:root
content: |
PVE_ROOT_PASSWORD_HASH='$6$DEGISTIRIN$REPLACE.WITH.YOUR.OWN.HASH'
PVE_NET_MANAGE=false
PVE_CLUSTER_MODE=none
- path: /etc/network/interfaces
permissions: "0644"
owner: root:root
content: |
auto lo
iface lo inet loopback
iface eno1 inet manual
iface eno2 inet manual
auto bond0
iface bond0 inet manual
bond-slaves eno1 eno2
bond-mode 802.3ad
bond-xmit-hash-policy layer3+4
bond-miimon 100
auto vmbr0
iface vmbr0 inet static
address 192.0.2.20/24
gateway 192.0.2.1
bridge-ports bond0
bridge-stp off
bridge-fd 0
bridge-vlan-aware yes
bridge-vids 2-4094
source /etc/network/interfaces.d/*
- path: /etc/cloud/cloud.cfg.d/99-pve-maas-disable-network.cfg
permissions: "0644"
content: |
network: {config: disabled}
runcmd:
- [ systemctl, enable, networking.service ]