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,285 @@
#!/bin/bash
#
# customize-proxmox.sh - packer-maas'in debian sablonu icinde, build VM'inde calisir.
#
# Debian cloud image uzerine Proxmox VE kurar ve MAAS ile deploy edilebilecek
# hale getirir. Bu dosya bir sablondur; Makefile @@...@@ yer tutucularini
# doldurur ve sonuna base64 kodlu overlay arsivini ekler.
#
# NOT: Bu script packer tarafindan "expect_disconnect = true" ile calistirilir.
#
set -euo pipefail
PVE_SUITE="@@PVE_SUITE@@"
PVE_REPO="@@PVE_REPO@@"
PVE_REPO_URI="@@PVE_REPO_URI@@"
PVE_KEYRING_URL="@@PVE_KEYRING_URL@@"
PVE_VERSION="@@PVE_VERSION@@"
PVE_EXTRA_PACKAGES="@@PVE_EXTRA_PACKAGES@@"
PACKER_MAAS_REF="@@PM_REF@@"
export DEBIAN_FRONTEND=noninteractive
APT="apt-get -y -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef"
# eatmydata kurulduktan sonra $APT bunun uzerinden calisir; dpkg'nin her paket
# icin yaptigi fsync'ler devre disi kalir. Imaj derlemede guvenli (VM diski
# zaten atilabilir) ve paket kurulumunu belirgin sekilde hizlandirir.
use_eatmydata() {
command -v eatmydata >/dev/null 2>&1 || return 0
APT="eatmydata ${APT}"
log "eatmydata etkin (dpkg fsync'leri devre disi)"
}
log() { echo "==> [pve-image] $*"; }
# ---------------------------------------------------------------------------
# 1. Gomulu overlay arsivini ac
# ---------------------------------------------------------------------------
log "Overlay dosyalari aciliyor"
sed -n '/^__PVE_MAAS_OVERLAY__$/,$p' "$0" | tail -n +2 | base64 -d \
| tar xzf - -C / --no-same-owner --no-same-permissions
chown -R root:root /usr/local/sbin/pve-maas-init /etc/pve-maas \
/etc/systemd/system/pve-maas-init.service /curtin
chmod 0755 /usr/local/sbin/pve-maas-init
chmod 0755 /curtin /curtin/curtin-hooks
chmod 0644 /etc/systemd/system/pve-maas-init.service
chmod 0644 /etc/pve-maas/pve-maas.conf
mkdir -p /etc/pve-maas/conf.d /var/lib/pve-maas
# ---------------------------------------------------------------------------
# 2. Build sirasinda hostname cozulebilir olmali (pve-cluster bunu ister)
# ---------------------------------------------------------------------------
BUILD_HOST="$(hostname -s)"
BUILD_IP="$(ip -4 -o route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1)"
: "${BUILD_IP:=127.0.1.1}"
log "Build hostname=${BUILD_HOST} ip=${BUILD_IP}"
cp -a /etc/hosts /etc/hosts.pve-image-backup
sed -i "/[[:space:]]${BUILD_HOST}\([[:space:]]\|$\)/d" /etc/hosts
echo "${BUILD_IP} ${BUILD_HOST}.local ${BUILD_HOST}" >> /etc/hosts
hostname -f || echo "UYARI: hostname -f cozulemedi" >&2
# ---------------------------------------------------------------------------
# 3. packer-maas'in kurdugu MAAS uyumlu cloud-init'i koru
#
# packer-maas, Debian'in cloud-init'i MAAS datasource'unu tam desteklemedigi
# icin Ubuntu build'ini kuruyor. full-upgrade bunu geri almasin.
# ---------------------------------------------------------------------------
log "cloud-init paketi hold'a aliniyor"
apt-mark hold cloud-init || true
# apt-cacher-ng gibi bir HTTP onbellegi verilmisse Debian depolarini https'ten
# http'ye cevir - aksi halde onbellek isabet etmez (CONNECT tunelini cache'leyemez).
# Paket imzalari yine dogrulandigi icin guvenlik kaybi yok.
if [ -n "${http_proxy:-}" ]; then
log "APT onbellegi kullaniliyor (${http_proxy}); Debian depolari http'ye cevriliyor"
sed -i 's|https://deb.debian.org|http://deb.debian.org|g; s|https://security.debian.org|http://security.debian.org|g' \
/etc/apt/sources.list /etc/apt/sources.list.d/*.sources /etc/apt/sources.list.d/*.list 2>/dev/null || true
fi
log "eatmydata kuruluyor"
$APT install eatmydata || true
use_eatmydata
# ---------------------------------------------------------------------------
# 4. Proxmox APT deposu
# ---------------------------------------------------------------------------
log "Proxmox anahtarligi indiriliyor: ${PVE_KEYRING_URL}"
curl -fsSL --retry 5 --retry-delay 3 "${PVE_KEYRING_URL}" \
-o /usr/share/keyrings/proxmox-archive-keyring.gpg
chmod 0644 /usr/share/keyrings/proxmox-archive-keyring.gpg
log "APT deposu ekleniyor: ${PVE_REPO_URI} ${PVE_SUITE} ${PVE_REPO}"
cat > /etc/apt/sources.list.d/proxmox.sources <<EOF
Types: deb
URIs: ${PVE_REPO_URI}
Suites: ${PVE_SUITE}
Components: ${PVE_REPO}
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
EOF
# Depo dogru mu, paketler gorunuyor mu?
apt-get update
apt-cache policy proxmox-ve | sed -n '1,6p'
if ! apt-cache show proxmox-ve >/dev/null 2>&1; then
echo "HATA: proxmox-ve paketi bulunamadi. PVE_SUITE=${PVE_SUITE} PVE_REPO=${PVE_REPO} dogru mu?" >&2
exit 1
fi
# ---------------------------------------------------------------------------
# 5. Sistemi guncelle
# ---------------------------------------------------------------------------
log "full-upgrade"
$APT full-upgrade
# ---------------------------------------------------------------------------
# 6. Etkilesimsiz kurulum icin debconf yanitlari
# ---------------------------------------------------------------------------
debconf-set-selections <<EOF
postfix postfix/main_mailer_type select Local only
postfix postfix/mailname string ${BUILD_HOST}.local
samba-common samba-common/dhcp boolean false
EOF
# ---------------------------------------------------------------------------
# 7. Proxmox cekirdegi + proxmox-ve
#
# Resmi rehber cekirdek kurulumundan sonra reboot onerir; bu reboot yalnizca
# ZFS/DKMS gibi calisan cekirdege bagimli islemler icin gereklidir. proxmox-ve
# paketleri kurulum sirasinda calisan cekirdege ihtiyac duymaz, bu yuzden imaj
# derlerken reboot atlaniyor. Dugum zaten ilk acilista PVE cekirdegi ile acilir.
# ---------------------------------------------------------------------------
# initramfs, cekirdek/firmware/dkms tetikleyicileriyle kurulum sirasinda
# defalarca yeniden uretiliyor. Kurulum boyunca devre disi birakip en sonda
# bir kez uretmek birkac dakika kazandiriyor.
log "update-initramfs gecici olarak devre disi"
dpkg-divert --local --rename --add /usr/sbin/update-initramfs >/dev/null
ln -sf /bin/true /usr/sbin/update-initramfs
log "proxmox-default-kernel kuruluyor"
$APT install proxmox-default-kernel
log "proxmox-ve kuruluyor"
$APT install proxmox-ve
if [ -n "${PVE_EXTRA_PACKAGES}" ]; then
log "Ek paketler kuruluyor: ${PVE_EXTRA_PACKAGES}"
# shellcheck disable=SC2086
$APT install ${PVE_EXTRA_PACKAGES}
fi
log "Kurulan surumler:"
dpkg-query -W -f='${Package} ${Version}\n' proxmox-ve pve-manager proxmox-default-kernel ifupdown2 || true
# ---------------------------------------------------------------------------
# 8. Debian cekirdegini ve os-prober'i kaldir
# ---------------------------------------------------------------------------
log "Debian cekirdegi ve os-prober kaldiriliyor"
$APT purge os-prober || true
# Yalnizca Debian'in kendi cekirdeklerini hedefle; Proxmox paketlerine dokunma.
DEB_KERNELS="$(dpkg-query -W -f='${Package}\n' 'linux-image*' 2>/dev/null \
| grep -E '^linux-image' | grep -vE 'pve|proxmox' || true)"
if [ -n "${DEB_KERNELS}" ]; then
log "Kaldirilacak: ${DEB_KERNELS}"
# shellcheck disable=SC2086
$APT purge ${DEB_KERNELS} || true
fi
$APT autoremove --purge || true
log "update-initramfs geri aliniyor ve bir kez calistiriliyor"
rm -f /usr/sbin/update-initramfs
dpkg-divert --local --rename --remove /usr/sbin/update-initramfs >/dev/null
update-initramfs -u -k all
update-grub
# ---------------------------------------------------------------------------
# 9. Abonelik gerektiren depolari devre disi birak (varsa)
# ---------------------------------------------------------------------------
for f in /etc/apt/sources.list.d/*enterprise*; do
[ -e "$f" ] || continue
log "Devre disi birakiliyor: $f"
mv "$f" "$f.disabled"
done
# ---------------------------------------------------------------------------
# 10. Dugum kimligini sifirla
#
# pmxcfs (/etc/pve) dugum adini config.db icinde saklar. Imaj build hostname'i
# ile olusur; MAAS baska bir hostname ile deploy edecegi icin veritabanini
# tamamen siliyoruz. pve-cluster ilk acilista guncel hostname ile yeni bir
# config.db uretir ve ExecStartPost'taki 'pvecm updatecerts' sertifikalari
# yeniden olusturur.
# ---------------------------------------------------------------------------
log "Proxmox dugum kimligi sifirlaniyor"
systemctl stop pve-guests pve-ha-lrm pve-ha-crm pvescheduler pvestatd pveproxy \
pvedaemon pve-firewall pvefw-logger spiceproxy corosync pve-cluster 2>/dev/null || true
sleep 2
umount /etc/pve 2>/dev/null || true
rm -f /var/lib/pve-cluster/config.db /var/lib/pve-cluster/config.db-wal \
/var/lib/pve-cluster/config.db-shm /var/lib/pve-cluster/.pmxcfs.lockfile
rm -rf /etc/corosync/* /var/lib/corosync/*
rm -rf /var/lib/rrdcached/db/*
rm -f /etc/pve/* 2>/dev/null || true
# iSCSI initiator adi dugume ozeldir; ilk acilista yeniden uretilir.
rm -f /etc/iscsi/initiatorname.iscsi
# Kume icin uretilen SSH bilgileri imaja sizmasin.
rm -f /root/.ssh/known_hosts /etc/ssh/ssh_known_hosts
find /root/.ssh -type l -delete 2>/dev/null || true
# ---------------------------------------------------------------------------
# 11. Ilk acilis servisini etkinlestir
# ---------------------------------------------------------------------------
log "pve-maas-init.service etkinlestiriliyor"
systemctl daemon-reload
systemctl enable pve-maas-init.service
# ---------------------------------------------------------------------------
# 12. Temizlik
# ---------------------------------------------------------------------------
# Kalici journald: dugum ilk acilista bir kez yeniden baslayabildigi icin
# (PVE_NET_APPLY=reboot) ucucu journal ile pve-maas-init kayitlari kayboluyor.
# ---------------------------------------------------------------------------
# Ilk acilista agi MAAS'a birak
#
# ifupdown2 kurulumu /etc/network/interfaces dosyasina build VM'inin arayuz
# adini yaziyor (or. "iface ens4 inet manual") ve networking.service etkin
# geliyor. Deploy edilen dugumde MAAS agi netplan/systemd-networkd ile
# yapilandirir; ayni anda ifupdown2 devreye girince gercek arayuzu kapatiyor
# ve dugum daha pve-maas-init calismadan agini kaybediyor.
#
# Cozum: imajda temiz/bos bir interfaces dosyasi birak ve networking.service'i
# devre disi birak. pve-maas-init ag asamasinda vmbr0'i yazip bu servisi
# kendisi etkinlestirir.
# ---------------------------------------------------------------------------
log "networking.service devre disi birakiliyor (ilk acilista ag MAAS'ta)"
cat > /etc/network/interfaces <<'EOF'
# Bu dosya pve-maas-init tarafindan, dugum ilk acilista vmbr0 koprusune
# gecirilirken yeniden yazilir. O ana kadar ag MAAS/cloud-init (netplan)
# tarafindan yonetilir; bu yuzden burada yalnizca loopback tanimlidir.
auto lo
iface lo inet loopback
source /etc/network/interfaces.d/*
EOF
rm -f /etc/network/interfaces.d/* 2>/dev/null || true
# Proxmox, ag degisikliklerini once /etc/network/interfaces.new dosyasina
# yaziyor; pvenetcommit.service acilista bunu interfaces uzerine TASIYOR.
# Build sirasinda olusan bir .new dosyasi imajda kalirsa dugumun ilk
# acilisinda temiz yapilandirmamizi ezer.
rm -f /etc/network/interfaces.new
systemctl disable networking.service 2>/dev/null || true
# Imajin kunyesi - dagitilmis bir dugumde "bu hangi imajdan geldi?" sorusunu
# cevaplar; 'make check-upstream' de bunu okur.
log "Imaj kunyesi yaziliyor: /etc/pve-maas/image-info"
{
echo "build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "pve_version=@@PVE_VERSION@@"
echo "debian_suite=${PVE_SUITE}"
echo "pve_repo=${PVE_REPO}"
echo "packer_maas_ref=${PACKER_MAAS_REF}"
dpkg-query -W -f='${Package}=${Version}\n' proxmox-ve pve-manager proxmox-default-kernel 2>/dev/null
echo "kernel=$(ls -1 /boot/vmlinuz-*-pve 2>/dev/null | sed 's|.*/vmlinuz-||' | head -1)"
} > /etc/pve-maas/image-info
cat /etc/pve-maas/image-info
log "Kalici journald etkinlestiriliyor"
mkdir -p /var/log/journal
systemd-tmpfiles --create --prefix /var/log/journal 2>/dev/null || true
log "Temizlik"
mv /etc/hosts.pve-image-backup /etc/hosts
rm -f /etc/postfix/main.cf.proto 2>/dev/null || true
[ -f /etc/postfix/main.cf ] && postconf -e "myhostname = localhost" >/dev/null 2>&1 || true
$APT clean
rm -rf /var/lib/apt/lists/*
find /var/log/journal -mindepth 1 -delete 2>/dev/null || true
rm -rf /var/log/*.gz /var/log/*.1
: > /var/log/wtmp || true
: > /var/log/btmp || true
cloud-init clean --logs || true
log "Imaj hazir: Proxmox VE ${PVE_VERSION} / Debian ${PVE_SUITE}"
exit 0
# Bu satirdan sonrasi Makefile tarafindan eklenen base64 overlay arsividir.

283
scripts/deploy-cluster.sh Executable file
View File

@@ -0,0 +1,283 @@
#!/bin/bash
#
# deploy-cluster.sh - MAAS uzerinden komple bir Proxmox VE kumesi kurar.
#
# Ilk dugumu 'create' modunda deploy eder, ayaga kalkmasini bekler, sertifika
# parmak izini okur ve kalan dugumleri o parmak iziyle 'join' modunda deploy eder.
#
# NEREDE CALISTIRILIR: MAAS region controller uzerinde (ya da 'maas' CLI profili
# tanimli ve dugumlerin 8006 portuna erisebilen bir makinede).
#
# Ornek:
# ./deploy-cluster.sh --name pve-prod --nodes pve1,pve2,pve3
# ./deploy-cluster.sh --name pve-dr --nodes dr1,dr2 --profile admin --dry-run
#
set -euo pipefail
PROFILE=admin
CLUSTER_NAME=""
NODES=""
DISTRO_SERIES=proxmox-ve-9
ROOT_PASSWORD=""
ROOT_PASSWORD_HASH=""
THINPOOL=auto
THINPOOL_MIN_GB=16
NET_APPLY=reboot
LINK0_PREFIX=""
DEPLOY_TIMEOUT=2400
PEER_TIMEOUT=1200
SERIAL=false
DRY_RUN=false
usage() {
# Bastaki yorum blogunu, ilk kod satirina kadar bas.
awk 'NR>1 && /^#/{sub(/^# ?/,""); print; next} NR>1 && !/^#/{exit}' "$0"
cat <<EOF
Secenekler:
--name <ad> Kume adi (zorunlu)
--nodes <a,b,c> MAAS hostname listesi; ILKI kumeyi olusturur (zorunlu)
--profile <ad> maas CLI profili (varsayilan: ${PROFILE})
--distro-series <ad> MAAS ozel imaj adi (varsayilan: ${DISTRO_SERIES})
--root-password <p> root@pam parolasi. Verilmezse uretilir ve ekrana yazilir.
--thinpool <deger> auto | off | <vg-adi> (varsayilan: ${THINPOOL})
--thinpool-min-gb <n> Thin havuz icin gereken en az bos alan (varsayilan: ${THINPOOL_MIN_GB})
--net-apply <mod> reboot | reload | none (varsayilan: ${NET_APPLY})
--link0-prefix <cidr> Corosync link0 icin ayri ag oneki, or. 10.10.20.
Dugumun o agdaki adresi otomatik bulunur.
--serial Katilan dugumleri teker teker deploy et (buyuk kumelerde)
--dry-run Hicbir sey deploy etme, uretilecek user-data'yi goster
-h, --help Bu yardim
Notlar:
* Kume dugumunun root parolasi API dogrulamasi icin DUZ METIN olarak
user-data'ya girer ve MAAS'ta saklanir. Kisa omurlu bir parola kullanip
kurulumdan sonra degistirin.
* Ayni imaj ve preseed ile birden fazla bagimsiz kume kurabilirsiniz;
kume kimligi yalnizca bu user-data'dan gelir.
EOF
}
while [ $# -gt 0 ]; do
case "$1" in
--name) CLUSTER_NAME="$2"; shift 2 ;;
--nodes) NODES="$2"; shift 2 ;;
--profile) PROFILE="$2"; shift 2 ;;
--distro-series) DISTRO_SERIES="$2"; shift 2 ;;
--root-password) ROOT_PASSWORD="$2"; shift 2 ;;
--thinpool) THINPOOL="$2"; shift 2 ;;
--thinpool-min-gb) THINPOOL_MIN_GB="$2"; shift 2 ;;
--net-apply) NET_APPLY="$2"; shift 2 ;;
--link0-prefix) LINK0_PREFIX="$2"; shift 2 ;;
--serial) SERIAL=true; shift ;;
--dry-run) DRY_RUN=true; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "Bilinmeyen secenek: $1" >&2; usage; exit 2 ;;
esac
done
[ -n "$CLUSTER_NAME" ] || { echo "HATA: --name zorunlu" >&2; exit 2; }
[ -n "$NODES" ] || { echo "HATA: --nodes zorunlu" >&2; exit 2; }
for c in maas jq openssl base64; do
command -v "$c" >/dev/null || { echo "HATA: '$c' bulunamadi" >&2; exit 1; }
done
IFS=',' read -r -a NODE_LIST <<< "$NODES"
FIRST="${NODE_LIST[0]}"
JOINERS=("${NODE_LIST[@]:1}")
log() { printf '\n\033[1m==> %s\033[0m\n' "$*"; }
info() { printf ' %s\n' "$*"; }
die() { printf '\n\033[1;31mHATA: %s\033[0m\n' "$*" >&2; exit 1; }
# ---------------------------------------------------------------- kimlik bilgileri
if [ -z "$ROOT_PASSWORD" ]; then
ROOT_PASSWORD="$(openssl rand -base64 15 | tr -d '/+=' | head -c 16)"
GENERATED=true
else
GENERATED=false
fi
ROOT_PASSWORD_HASH="$(openssl passwd -6 "$ROOT_PASSWORD")"
# ---------------------------------------------------------------- MAAS yardimcilari
maas_get() { maas "$PROFILE" machines read hostname="$1" 2>/dev/null | jq -r ".[0].$2 // empty"; }
require_ready() {
local h st
for h in "${NODE_LIST[@]}"; do
st="$(maas_get "$h" status_name)"
[ -n "$st" ] || die "MAAS'ta '$h' adinda makine yok"
[ "$st" = "Ready" ] || die "'$h' durumu '$st' - deploy icin 'Ready' olmali (once release edin)"
info "$h: Ready"
done
}
wait_status() {
local h="$1" want="$2" timeout="$3" waited=0 st
while [ "$waited" -lt "$timeout" ]; do
st="$(maas_get "$h" status_name)"
case "$st" in
"$want") return 0 ;;
Failed*|Broken*) die "'$h' durumu '$st' - MAAS olaylarina bakin" ;;
esac
sleep 20; waited=$((waited + 20))
done
die "'$h' ${timeout}s icinde '$want' durumuna gelmedi (son durum: ${st:-bilinmiyor})"
}
node_ip() { maas "$PROFILE" machines read hostname="$1" 2>/dev/null | jq -r '.[0].ip_addresses[0] // empty'; }
wait_port() {
local ip="$1" port="$2" timeout="$3" waited=0
while [ "$waited" -lt "$timeout" ]; do
if timeout 4 bash -c "exec 3<>/dev/tcp/${ip}/${port}" 2>/dev/null; then return 0; fi
sleep 10; waited=$((waited + 10))
done
die "${ip}:${port} ${timeout}s icinde acilmadi"
}
fingerprint_of() {
openssl s_client -connect "$1:8006" -servername "$1" </dev/null 2>/dev/null \
| openssl x509 -noout -fingerprint -sha256 2>/dev/null | cut -d= -f2
}
# Dugumun link0 oneki ile eslesen adresini MAAS'tan bul.
link0_of() {
[ -n "$LINK0_PREFIX" ] || return 0
maas "$PROFILE" machines read hostname="$1" 2>/dev/null \
| jq -r --arg p "$LINK0_PREFIX" '.[0].ip_addresses[]? | select(startswith($p))' | head -1
}
# ---------------------------------------------------------------- user-data uretimi
render_userdata() {
local mode="$1" host="$2" peer="${3:-}" fp="${4:-}" link0
link0="$(link0_of "$host")"
cat <<EOF
#cloud-config
# ${host} - kume '${CLUSTER_NAME}' (${mode})
# deploy-cluster.sh tarafindan uretildi
write_files:
- path: /etc/pve-maas/conf.d/50-pve.conf
permissions: "0600"
owner: root:root
content: |
PVE_ROOT_PASSWORD_HASH='${ROOT_PASSWORD_HASH}'
PVE_NET_APPLY=${NET_APPLY}
PVE_THINPOOL=${THINPOOL}
PVE_THINPOOL_MIN_GB=${THINPOOL_MIN_GB}
EOF
if [ "$mode" = "create" ]; then
cat <<EOF
PVE_CLUSTER_MODE=create
PVE_CLUSTER_NAME=${CLUSTER_NAME}
EOF
else
cat <<EOF
PVE_CLUSTER_MODE=join
PVE_CLUSTER_PEER=${peer}
PVE_CLUSTER_PEER_PASSWORD='${ROOT_PASSWORD}'
PVE_CLUSTER_FINGERPRINT='${fp}'
EOF
fi
[ -n "$link0" ] && echo " PVE_CLUSTER_LINK0=${link0}"
return 0
}
deploy_node() {
local host="$1" ud="$2" sid
sid="$(maas_get "$host" system_id)"
maas "$PROFILE" machine deploy "$sid" \
osystem=custom "distro_series=${DISTRO_SERIES}" \
"user_data=$(printf '%s' "$ud" | base64 -w0)" >/dev/null
}
# ---------------------------------------------------------------- akis
log "Kume: ${CLUSTER_NAME}"
info "ilk dugum (create) : ${FIRST}"
info "katilanlar (join) : ${JOINERS[*]:-yok}"
info "imaj : custom/${DISTRO_SERIES}"
[ -n "$LINK0_PREFIX" ] && info "corosync link0 oneki: ${LINK0_PREFIX}"
if [ "$DRY_RUN" = true ]; then
log "DRY RUN - hicbir sey deploy edilmiyor"
echo "--- ${FIRST} (create) ---"
render_userdata create "$FIRST"
for h in "${JOINERS[@]:-}"; do
[ -n "$h" ] || continue
echo "--- ${h} (join) ---"
render_userdata join "$h" "<ilk-dugumun-IP-si>" "<parmak-izi>"
done
echo
echo "root@pam parolasi: ${ROOT_PASSWORD}"
exit 0
fi
log "Makineler kontrol ediliyor"
require_ready
log "${FIRST} deploy ediliyor (kume olusturuluyor)"
deploy_node "$FIRST" "$(render_userdata create "$FIRST")"
wait_status "$FIRST" Deployed "$DEPLOY_TIMEOUT"
info "MAAS deploy tamamlandi"
FIRST_IP="$(node_ip "$FIRST")"
[ -n "$FIRST_IP" ] || die "'$FIRST' icin IP bulunamadi"
info "IP: ${FIRST_IP}"
info "Proxmox arayuzunun acilmasi bekleniyor (ag donusumu icin bir kez yeniden baslar)"
wait_port "$FIRST_IP" 8006 "$PEER_TIMEOUT"
FP="$(fingerprint_of "$FIRST_IP")"
[ -n "$FP" ] || die "'$FIRST' sertifika parmak izi okunamadi"
info "parmak izi: ${FP}"
if [ "${#JOINERS[@]}" -eq 0 ] || [ -z "${JOINERS[0]:-}" ]; then
log "Katilacak baska dugum yok"
else
log "Katilan dugumler deploy ediliyor"
for h in "${JOINERS[@]}"; do
info "deploy: $h"
deploy_node "$h" "$(render_userdata join "$h" "$FIRST_IP" "$FP")"
if [ "$SERIAL" = true ]; then
wait_status "$h" Deployed "$DEPLOY_TIMEOUT"
info "$h: MAAS deploy tamamlandi, kumeye katilmasi bekleniyor"
sleep 60
fi
done
[ "$SERIAL" = false ] && {
for h in "${JOINERS[@]}"; do wait_status "$h" Deployed "$DEPLOY_TIMEOUT"; info "$h: deploy tamamlandi"; done
}
fi
log "Kume durumu bekleniyor"
EXPECTED="${#NODE_LIST[@]}"
# Tum dugumlerin 8006'da yanit vermesini bekle - kume uyeligini asagidaki
# talimatla dugum uzerinden dogrulayin (bu script dugumlere SSH yapmaz).
for h in "${NODE_LIST[@]}"; do
ip="$(node_ip "$h")"
if [ -n "$ip" ] && wait_port "$ip" 8006 300 2>/dev/null; then
info "$h ($ip): Proxmox arayuzu acik"
else
info "$h: 8006 acilmadi - 'journalctl -u pve-maas-init -b' ile bakin"
fi
done
cat <<EOF
Kume '${CLUSTER_NAME}' kuruldu.
Ilk dugum : ${FIRST} (${FIRST_IP})
Web arayuzu : https://${FIRST_IP}:8006/
Kullanici : root@pam
Parola : ${ROOT_PASSWORD}$([ "$GENERATED" = true ] && echo " <- uretildi, kaydedin")
Dogrulamak icin bir dugumde:
pvecm status # 'Nodes: ${EXPECTED}' ve 'Quorate: Yes' bekleniyor
pvecm nodes
Dugumler kumeye katilmadiysa ilgili dugumde:
journalctl -u pve-maas-init -b
EOF

55
scripts/install-deps.sh Executable file
View File

@@ -0,0 +1,55 @@
#!/bin/bash
#
# install-deps.sh - Ubuntu 22.04+ build host'una packer-maas bagimliliklarini kurar.
#
# Kullanim: sudo ./scripts/install-deps.sh
#
set -euo pipefail
if [ "$(id -u)" -ne 0 ]; then
echo "Bu script root olarak calistirilmali: sudo $0" >&2
exit 1
fi
. /etc/os-release
if [ "${ID:-}" != "ubuntu" ] && [ "${ID_LIKE:-}" != "debian" ]; then
echo "UYARI: Bu script Ubuntu/Debian icin yazildi (bulunan: ${PRETTY_NAME:-bilinmiyor})." >&2
fi
export DEBIAN_FRONTEND=noninteractive
echo "==> Temel paketler kuruluyor"
apt-get update
apt-get install -y --no-install-recommends \
ca-certificates curl gpg git make parted pigz jq \
qemu-system-x86 qemu-utils ovmf cloud-image-utils \
libnbd-bin nbdkit fuse2fs cpu-checker
echo "==> HashiCorp APT deposu ekleniyor (packer)"
install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://apt.releases.hashicorp.com/gpg \
| gpg --dearmor --yes -o /etc/apt/keyrings/hashicorp-archive-keyring.gpg
chmod 0644 /etc/apt/keyrings/hashicorp-archive-keyring.gpg
cat > /etc/apt/sources.list.d/hashicorp.list <<REPO
deb [signed-by=/etc/apt/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com ${UBUNTU_CODENAME:-${VERSION_CODENAME}} main
REPO
apt-get update
apt-get install -y packer
echo "==> KVM kontrolu"
if ! kvm-ok; then
echo "HATA: KVM kullanilamiyor. VM'de nested virtualization acik mi? (Proxmox: cpu=host)" >&2
exit 1
fi
# Build root olarak kosuyor ama kullaniciyi da kvm grubuna alalim.
TARGET_USER="${SUDO_USER:-}"
if [ -n "$TARGET_USER" ] && [ "$TARGET_USER" != "root" ]; then
adduser "$TARGET_USER" kvm >/dev/null 2>&1 || true
fi
echo
echo "==> Hazir. Surumler:"
packer version
qemu-system-x86_64 --version | head -1

99
scripts/verify-image.sh Executable file
View File

@@ -0,0 +1,99 @@
#!/bin/bash
#
# verify-image.sh - Uretilen MAAS tgz imajinin beklenen icerige sahip oldugunu dogrular.
#
# Kullanim: ./scripts/verify-image.sh build/proxmox-ve-9.tar.gz
#
set -uo pipefail
IMG="${1:-}"
[ -n "$IMG" ] && [ -f "$IMG" ] || { echo "Kullanim: $0 <imaj.tar.gz>" >&2; exit 2; }
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
echo "==> Imaj: $IMG ($(du -h "$IMG" | cut -f1))"
echo "==> Icerik listesi cikariliyor..."
tar tzf "$IMG" > "$TMP/list" || { echo "HATA: arsiv okunamadi"; exit 1; }
echo " $(wc -l < "$TMP/list") giris"
pass=0; fail=0
have() { grep -qx "\./$1" "$TMP/list" || grep -q "^\./$1$" "$TMP/list"; }
present() { grep -q "^\./$1" "$TMP/list"; }
check() {
local desc="$1" cond="$2"
if eval "$cond"; then
printf ' [ OK ] %s\n' "$desc"; pass=$((pass+1))
else
printf ' [FAIL] %s\n' "$desc"; fail=$((fail+1))
fi
}
echo
echo "==> Dosya varligi kontrolleri"
check "pve-maas-init mevcut" 'present "usr/local/sbin/pve-maas-init"'
check "pve-maas.conf mevcut" 'present "etc/pve-maas/pve-maas.conf"'
check "systemd unit mevcut" 'present "etc/systemd/system/pve-maas-init.service"'
check "unit multi-user.target icin etkin" 'present "etc/systemd/system/multi-user.target.wants/pve-maas-init.service"'
check "Proxmox APT deposu mevcut" 'present "etc/apt/sources.list.d/proxmox.sources"'
check "Proxmox anahtarligi mevcut" 'present "usr/share/keyrings/proxmox-archive-keyring.gpg"'
check "pveproxy ikilisi mevcut" 'present "usr/bin/pveproxy"'
check "pvecm ikilisi mevcut" 'present "usr/bin/pvecm"'
check "pvesh ikilisi mevcut" 'present "usr/bin/pvesh"'
check "ifupdown2 mevcut" 'present "usr/share/ifupdown2"'
check "cloud-init mevcut" 'present "usr/bin/cloud-init"'
check "curtin-hooks mevcut" 'present "curtin/curtin-hooks"'
echo
echo "==> Olmamasi gerekenler"
check "pmxcfs config.db yok (dugum kimligi temiz)" '! present "var/lib/pve-cluster/config.db"'
check "corosync yapilandirmasi yok" '! present "etc/corosync/corosync.conf"'
check "iSCSI initiator adi yok (dugumde uretilir)" '! present "etc/iscsi/initiatorname.iscsi"'
check "SSH host anahtarlari yok" '! grep -qE "^\./etc/ssh/ssh_host_.*_key$" "$TMP/list"'
check "networking.service etkin DEGIL" '! present "etc/systemd/system/multi-user.target.wants/networking.service"'
check "interfaces.new yok (pvenetcommit ezmesin)" '! present "etc/network/interfaces.new"'
check "Debian cekirdegi yok" '! grep -qE "^\./boot/vmlinuz-.*[^e]-(cloud-)?amd64$" "$TMP/list"'
echo
echo "==> Proxmox cekirdegi"
if grep -qE '^\./boot/vmlinuz-.*-pve$' "$TMP/list"; then
printf ' [ OK ] PVE cekirdegi: %s\n' "$(grep -oE 'vmlinuz-[^ ]*-pve' "$TMP/list" | head -1)"
pass=$((pass+1))
else
printf ' [FAIL] /boot altinda *-pve cekirdegi bulunamadi\n'; fail=$((fail+1))
fi
echo
echo "==> Ayiklanan dosya icerikleri"
tar xzf "$IMG" -C "$TMP" \
./etc/apt/sources.list.d/proxmox.sources \
./usr/local/sbin/pve-maas-init \
./etc/pve-maas/pve-maas.conf 2>/dev/null
tar xzf "$IMG" -C "$TMP" ./etc/network/interfaces 2>/dev/null
if [ -f "$TMP/etc/network/interfaces" ]; then
echo "--- /etc/network/interfaces ---"
sed 's/^/ /' "$TMP/etc/network/interfaces"
if grep -qE '^\s*(auto|iface)\s+(?!lo)' "$TMP/etc/network/interfaces" 2>/dev/null \
|| grep -qE '^[[:space:]]*iface[[:space:]]+[^l ]' "$TMP/etc/network/interfaces"; then
printf ' [FAIL] interfaces dosyasinda build VM artigi arayuz var\n'; fail=$((fail+1))
else
printf ' [ OK ] interfaces yalnizca loopback iceriyor\n'; pass=$((pass+1))
fi
fi
if [ -f "$TMP/etc/apt/sources.list.d/proxmox.sources" ]; then
echo "--- proxmox.sources ---"
sed 's/^/ /' "$TMP/etc/apt/sources.list.d/proxmox.sources"
fi
if [ -x "$TMP/usr/local/sbin/pve-maas-init" ]; then
printf ' [ OK ] pve-maas-init calistirilabilir\n'; pass=$((pass+1))
else
printf ' [FAIL] pve-maas-init calistirilabilir degil\n'; fail=$((fail+1))
fi
echo
echo "==> Sonuc: ${pass} basarili, ${fail} basarisiz"
[ "$fail" -eq 0 ]