Parts of this repository are derived from canonical/packer-maas, which Canonical
distributes under the AGPLv3, so its copyleft carries over and a permissive or
plain-GPL licence is not available:
* maas/curtin_userdata_custom.in is adapted from upstream's
debian/curtin_userdata_custom_amd64, with several late_commands copied
verbatim (the PXE-disable call, the target bind mount, the cloud.cfg rewrite
and the zz-update-grub fix)
* overlay/curtin/curtin-hooks follows upstream's debian/scripts/curtin-hooks:
same imports, same load_command_environment -> load_command_config ->
builtin_curthooks -> cleanup structure, near-identical cleanup(). The
kernel-disabling and interface-pinning functions are original.
The upstream template itself is not vendored; it is cloned at build time and
pinned by PM_REF.
Adds the full AGPL-3.0 text as LICENSE and SPDX-License-Identifier headers to
every source file, placed after the shebang or the #cloud-config marker so both
keep working. deploy-cluster.sh's --help filters the new header lines out of the
usage text it extracts from its own comment block.
GitHub Pages serves index.md, which includes README.md, so the site cannot drift
from the repository documentation. Nothing but build/ is excluded, which keeps
the README's relative links to LICENSE, scripts/ and maas/examples/ resolving on
the published site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
639 lines
24 KiB
Bash
Executable File
639 lines
24 KiB
Bash
Executable File
#!/bin/bash
|
|
# Copyright (C) 2026 Ilker Manap
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
#
|
|
# pve-maas-init - MAAS ile deploy edilen Proxmox VE dugumunu ilk acilista yapilandirir.
|
|
#
|
|
# Asamalar (her biri /var/lib/pve-maas/<asama>.done ile bir kez calisir):
|
|
# hosts - hostname/FQDN'i yonetim IP'sine bagla (pvecm bunu ister)
|
|
# identity - dugume ozel kimlikleri yeniden uret (iSCSI IQN vb.)
|
|
# rootpw - root@pam parolasini ayarla (web arayuzu ve kume katilimi icin)
|
|
# network - MAAS'in verdigi arayuzu vmbr0 koprusune donustur
|
|
# cluster - kume olustur veya mevcut kumeye katil
|
|
# storage - bos alanda LVM-thin havuzu olustur (local-lvm)
|
|
#
|
|
# Yapilandirma: /etc/pve-maas/pve-maas.conf ve /etc/pve-maas/conf.d/*.conf
|
|
# (conf.d icerigi MAAS deploy sirasinda cloud-init write_files ile yazilir)
|
|
#
|
|
set -uo pipefail
|
|
|
|
CONF_DIR=/etc/pve-maas
|
|
STATE_DIR=/var/lib/pve-maas
|
|
LOG_TAG=pve-maas-init
|
|
|
|
mkdir -p "$STATE_DIR" "$CONF_DIR/conf.d"
|
|
|
|
log() { echo "[$LOG_TAG] $*"; logger -t "$LOG_TAG" -- "$*" 2>/dev/null || true; }
|
|
warn() { echo "[$LOG_TAG] UYARI: $*" >&2; logger -t "$LOG_TAG" -p user.warning -- "UYARI: $*" 2>/dev/null || true; }
|
|
die() { echo "[$LOG_TAG] HATA: $*" >&2; logger -t "$LOG_TAG" -p user.err -- "HATA: $*" 2>/dev/null || true; exit 1; }
|
|
|
|
done_flag() { echo "$STATE_DIR/$1.done"; }
|
|
is_done() { [ -e "$(done_flag "$1")" ]; }
|
|
mark_done() { date -Is > "$(done_flag "$1")"; }
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Yapilandirmayi yukle
|
|
# ---------------------------------------------------------------------------
|
|
load_config() {
|
|
# shellcheck disable=SC1091
|
|
[ -r "$CONF_DIR/pve-maas.conf" ] && . "$CONF_DIR/pve-maas.conf"
|
|
local f
|
|
for f in "$CONF_DIR"/conf.d/*.conf; do
|
|
[ -r "$f" ] || continue
|
|
log "yapilandirma yukleniyor: $f"
|
|
# shellcheck disable=SC1090
|
|
. "$f"
|
|
done
|
|
|
|
# Varsayilanlar
|
|
PVE_ENABLED="${PVE_ENABLED:-true}"
|
|
PVE_FQDN="${PVE_FQDN:-}"
|
|
|
|
PVE_NET_MANAGE="${PVE_NET_MANAGE:-true}"
|
|
PVE_NET_BRIDGE="${PVE_NET_BRIDGE:-vmbr0}"
|
|
PVE_NET_UPLINK="${PVE_NET_UPLINK:-}"
|
|
PVE_NET_MODE="${PVE_NET_MODE:-auto}" # auto | static | dhcp
|
|
PVE_NET_APPLY="${PVE_NET_APPLY:-reboot}" # reboot | reload | none
|
|
PVE_NET_VLAN_AWARE="${PVE_NET_VLAN_AWARE:-false}"
|
|
PVE_NET_EXTRA="${PVE_NET_EXTRA:-}"
|
|
|
|
PVE_ROOT_PASSWORD="${PVE_ROOT_PASSWORD:-}"
|
|
PVE_ROOT_PASSWORD_HASH="${PVE_ROOT_PASSWORD_HASH:-}"
|
|
|
|
PVE_CLUSTER_MODE="${PVE_CLUSTER_MODE:-none}" # none | create | join
|
|
PVE_CLUSTER_NAME="${PVE_CLUSTER_NAME:-}"
|
|
PVE_CLUSTER_PEER="${PVE_CLUSTER_PEER:-}"
|
|
PVE_CLUSTER_PEER_PASSWORD="${PVE_CLUSTER_PEER_PASSWORD:-}"
|
|
PVE_CLUSTER_PEER_PASSWORD_FILE="${PVE_CLUSTER_PEER_PASSWORD_FILE:-}"
|
|
PVE_CLUSTER_FINGERPRINT="${PVE_CLUSTER_FINGERPRINT:-}"
|
|
PVE_CLUSTER_FINGERPRINT_DISCOVER="${PVE_CLUSTER_FINGERPRINT_DISCOVER:-true}"
|
|
PVE_CLUSTER_LINK0="${PVE_CLUSTER_LINK0:-}"
|
|
PVE_CLUSTER_LINK1="${PVE_CLUSTER_LINK1:-}"
|
|
PVE_CLUSTER_NODEID="${PVE_CLUSTER_NODEID:-}"
|
|
PVE_CLUSTER_VOTES="${PVE_CLUSTER_VOTES:-}"
|
|
PVE_CLUSTER_WAIT="${PVE_CLUSTER_WAIT:-900}"
|
|
PVE_CLUSTER_RETRIES="${PVE_CLUSTER_RETRIES:-5}"
|
|
PVE_CLUSTER_WIPE_SECRETS="${PVE_CLUSTER_WIPE_SECRETS:-true}"
|
|
|
|
PVE_THINPOOL="${PVE_THINPOOL:-auto}" # auto | off | <vg-adi>
|
|
PVE_THINPOOL_NAME="${PVE_THINPOOL_NAME:-data}"
|
|
PVE_THINPOOL_STORAGE="${PVE_THINPOOL_STORAGE:-local-lvm}"
|
|
PVE_THINPOOL_MIN_GB="${PVE_THINPOOL_MIN_GB:-16}"
|
|
PVE_THINPOOL_DISK="${PVE_THINPOOL_DISK:-}"
|
|
PVE_THINPOOL_VG="${PVE_THINPOOL_VG:-pve}"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Yardimcilar
|
|
# ---------------------------------------------------------------------------
|
|
primary_iface() {
|
|
if [ -n "$PVE_NET_UPLINK" ]; then echo "$PVE_NET_UPLINK"; return; fi
|
|
ip -4 -o route show default 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") {print $(i+1); exit}}'
|
|
}
|
|
|
|
primary_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); exit}}'
|
|
}
|
|
|
|
node_fqdn() {
|
|
local host fqdn dom
|
|
host="$(hostname -s)"
|
|
if [ -n "$PVE_FQDN" ]; then echo "$PVE_FQDN"; return; fi
|
|
fqdn="$(hostname -f 2>/dev/null || true)"
|
|
if [ -n "$fqdn" ] && [ "$fqdn" != "$host" ]; then echo "$fqdn"; return; fi
|
|
dom="$(awk '/^(search|domain)[[:space:]]/{print $2; exit}' /etc/resolv.conf 2>/dev/null || true)"
|
|
if [ -n "$dom" ]; then echo "${host}.${dom}"; else echo "$host"; fi
|
|
}
|
|
|
|
wait_for_cloud_init() {
|
|
command -v cloud-init >/dev/null 2>&1 || return 0
|
|
log "cloud-init'in bitmesi bekleniyor"
|
|
# Unit'te After=cloud-final.service kullanamiyoruz (siralama dongusu),
|
|
# bu yuzden beklemeyi burada yapiyoruz. Hata donmesi normal olabilir.
|
|
timeout 900 cloud-init status --wait >/dev/null 2>&1
|
|
log "cloud-init durumu: $(cloud-init status 2>/dev/null | head -1)"
|
|
return 0
|
|
}
|
|
|
|
wait_for_network() {
|
|
local i ip
|
|
for ((i = 0; i < 120; i += 5)); do
|
|
ip="$(primary_ip)"
|
|
[ -n "$ip" ] && { log "yonetim IP'si: ${ip}"; return 0; }
|
|
sleep 5
|
|
done
|
|
return 1
|
|
}
|
|
|
|
wait_for_pmxcfs() {
|
|
local i
|
|
for i in $(seq 1 60); do
|
|
[ -e /etc/pve/local ] && return 0
|
|
sleep 2
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Asama: hosts
|
|
# ---------------------------------------------------------------------------
|
|
stage_hosts() {
|
|
is_done hosts && return 0
|
|
local host fqdn ip
|
|
host="$(hostname -s)"
|
|
fqdn="$(node_fqdn)"
|
|
ip="$(primary_ip)"
|
|
[ -n "$ip" ] || { warn "yonetim IP'si bulunamadi, /etc/hosts atlaniyor"; return 0; }
|
|
|
|
log "/etc/hosts guncelleniyor: ${ip} ${fqdn} ${host}"
|
|
cp -a /etc/hosts "$STATE_DIR/hosts.orig" 2>/dev/null || true
|
|
|
|
local keep
|
|
keep="$(grep -vE "^[[:space:]]*(127\.0\.0\.1|127\.0\.1\.1|::1|ff02::[12])[[:space:]]" /etc/hosts 2>/dev/null \
|
|
| grep -vE "[[:space:]]${host}([[:space:]]|\$)" || true)"
|
|
|
|
{
|
|
echo "127.0.0.1 localhost.localdomain localhost"
|
|
echo "${ip} ${fqdn} ${host}"
|
|
[ -n "$keep" ] && echo "$keep"
|
|
echo
|
|
echo "# IPv6"
|
|
echo "::1 localhost ip6-localhost ip6-loopback"
|
|
echo "ff02::1 ip6-allnodes"
|
|
echo "ff02::2 ip6-allrouters"
|
|
} > /etc/hosts
|
|
|
|
if command -v postconf >/dev/null 2>&1 && [ -f /etc/postfix/main.cf ]; then
|
|
postconf -e "myhostname = ${fqdn}" >/dev/null 2>&1 || true
|
|
systemctl try-restart postfix >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
# cloud-init manage_etc_hosts=true ise her acilista /etc/hosts'u
|
|
# 127.0.1.1 satiriyla geri yazar ve duzeltmemizi bozar.
|
|
cat > /etc/cloud/cloud.cfg.d/99-pve-maas-hosts.cfg <<'EOF'
|
|
# /etc/hosts artik pve-maas-init tarafindan yonetiliyor.
|
|
manage_etc_hosts: false
|
|
EOF
|
|
|
|
hostname -f >/dev/null 2>&1 || warn "hostname -f hala cozulmuyor"
|
|
|
|
# pmxcfs dugum kimligini ve sertifikalari dogru hostname/IP ile yenile.
|
|
if systemctl list-unit-files pve-cluster.service >/dev/null 2>&1; then
|
|
log "pve servisleri yeniden baslatiliyor (dogru hostname ile)"
|
|
systemctl restart pve-cluster >/dev/null 2>&1 || true
|
|
systemctl restart pvedaemon pveproxy pvestatd >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
mark_done hosts
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Asama: identity - dugume ozel kimlikler
|
|
# ---------------------------------------------------------------------------
|
|
stage_identity() {
|
|
is_done identity && return 0
|
|
if [ ! -s /etc/iscsi/initiatorname.iscsi ] && command -v iscsi-iname >/dev/null 2>&1; then
|
|
log "iSCSI initiator adi uretiliyor"
|
|
mkdir -p /etc/iscsi
|
|
echo "InitiatorName=$(iscsi-iname)" > /etc/iscsi/initiatorname.iscsi
|
|
chmod 0600 /etc/iscsi/initiatorname.iscsi
|
|
systemctl try-restart iscsid open-iscsi >/dev/null 2>&1 || true
|
|
fi
|
|
mark_done identity
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Asama: rootpw
|
|
# ---------------------------------------------------------------------------
|
|
stage_rootpw() {
|
|
is_done rootpw && return 0
|
|
if [ -n "$PVE_ROOT_PASSWORD_HASH" ]; then
|
|
log "root parolasi (hash) ayarlaniyor"
|
|
usermod -p "$PVE_ROOT_PASSWORD_HASH" root
|
|
elif [ -n "$PVE_ROOT_PASSWORD" ]; then
|
|
log "root parolasi ayarlaniyor"
|
|
echo "root:${PVE_ROOT_PASSWORD}" | chpasswd
|
|
else
|
|
warn "PVE_ROOT_PASSWORD tanimli degil; root@pam ile web arayuzune girilemez"
|
|
warn "ve bu dugum baska bir dugumun kumeye katilmasi icin hedef olamaz."
|
|
mark_done rootpw
|
|
return 0
|
|
fi
|
|
mark_done rootpw
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Asama: network - MAAS arayuzunu vmbr0'a cevir
|
|
# ---------------------------------------------------------------------------
|
|
detect_net_mode() {
|
|
[ "$PVE_NET_MODE" != "auto" ] && { echo "$PVE_NET_MODE"; return; }
|
|
if grep -rqsE '^\s*(dhcp4|dhcp6)\s*:\s*(true|yes)' /etc/netplan/ 2>/dev/null; then
|
|
echo dhcp; return
|
|
fi
|
|
if grep -rqsE '^\s*iface\s+\S+\s+inet6?\s+dhcp' /etc/network/interfaces.d/ 2>/dev/null; then
|
|
echo dhcp; return
|
|
fi
|
|
echo static
|
|
}
|
|
|
|
neutralize_foreign_netconf() {
|
|
log "MAAS/cloud-init ag yapilandirmasi devre disi birakiliyor"
|
|
printf 'network: {config: disabled}\n' > /etc/cloud/cloud.cfg.d/99-pve-maas-disable-network.cfg
|
|
|
|
local d
|
|
d=/etc/network/interfaces.d/disabled-by-pve-maas
|
|
mkdir -p "$d"
|
|
find /etc/network/interfaces.d -maxdepth 1 -type f -print0 2>/dev/null \
|
|
| xargs -0 -r -I{} mv {} "$d/"
|
|
|
|
if compgen -G "/etc/netplan/*.yaml" >/dev/null || compgen -G "/etc/netplan/*.yml" >/dev/null; then
|
|
mkdir -p /etc/netplan/disabled-by-pve-maas
|
|
mv /etc/netplan/*.y*ml /etc/netplan/disabled-by-pve-maas/ 2>/dev/null || true
|
|
fi
|
|
|
|
if systemctl is-enabled systemd-networkd >/dev/null 2>&1; then
|
|
systemctl disable --now systemd-networkd systemd-networkd.socket >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
# Proxmox statik /etc/resolv.conf bekler.
|
|
if [ -L /etc/resolv.conf ]; then
|
|
local ns search
|
|
ns="$(resolvectl dns 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9a-fA-F:.]+$' | sort -u || true)"
|
|
[ -z "$ns" ] && ns="$(awk '/^nameserver/{print $2}' /etc/resolv.conf | sort -u || true)"
|
|
search="$(awk '/^(search|domain)[[:space:]]/{$1=""; print substr($0,2); exit}' /etc/resolv.conf || true)"
|
|
if [ -n "$ns" ]; then
|
|
rm -f /etc/resolv.conf
|
|
{
|
|
[ -n "$search" ] && echo "search ${search}"
|
|
echo "$ns" | while read -r s; do [ -n "$s" ] && echo "nameserver $s"; done
|
|
} > /etc/resolv.conf
|
|
systemctl disable --now systemd-resolved >/dev/null 2>&1 || true
|
|
else
|
|
warn "nameserver bulunamadi, /etc/resolv.conf oldugu gibi birakiliyor"
|
|
fi
|
|
fi
|
|
|
|
systemctl enable networking.service >/dev/null 2>&1 || true
|
|
}
|
|
|
|
stage_network() {
|
|
is_done network && return 0
|
|
[ "$PVE_NET_MANAGE" = "true" ] || { log "ag yonetimi kapali (PVE_NET_MANAGE=false)"; mark_done network; return 0; }
|
|
|
|
local br uplink
|
|
br="$PVE_NET_BRIDGE"
|
|
uplink="$(primary_iface)"
|
|
|
|
if ip link show "$br" >/dev/null 2>&1 && [ "$uplink" = "$br" ]; then
|
|
log "$br zaten mevcut ve varsayilan rotayi tasiyor, ag donusumu atlaniyor"
|
|
mark_done network
|
|
return 0
|
|
fi
|
|
[ -n "$uplink" ] || { warn "uplink arayuzu bulunamadi, ag donusumu atlaniyor"; mark_done network; return 0; }
|
|
|
|
local mode cidr gw cidr6 gw6
|
|
mode="$(detect_net_mode)"
|
|
cidr="$(ip -4 -o addr show dev "$uplink" scope global 2>/dev/null | awk '{print $4; exit}')"
|
|
gw="$(ip -4 -o route show default dev "$uplink" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="via") {print $(i+1); exit}}')"
|
|
cidr6="$(ip -6 -o addr show dev "$uplink" scope global -deprecated 2>/dev/null | awk '{print $4; exit}')"
|
|
gw6="$(ip -6 -o route show default dev "$uplink" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="via") {print $(i+1); exit}}')"
|
|
|
|
if [ "$mode" = "static" ] && [ -z "$cidr" ]; then
|
|
warn "$uplink uzerinde IPv4 adresi yok, DHCP'ye dusuluyor"
|
|
mode=dhcp
|
|
fi
|
|
|
|
log "ag donusumu: ${uplink} -> ${br} (mod=${mode} adres=${cidr:-dhcp} gw=${gw:-yok})"
|
|
cp -a /etc/network/interfaces "$STATE_DIR/interfaces.orig" 2>/dev/null || true
|
|
neutralize_foreign_netconf
|
|
|
|
{
|
|
echo "# /etc/network/interfaces - pve-maas-init tarafindan olusturuldu"
|
|
echo "# Kaynak yedegi: $STATE_DIR/interfaces.orig"
|
|
echo
|
|
echo "auto lo"
|
|
echo "iface lo inet loopback"
|
|
echo
|
|
echo "iface ${uplink} inet manual"
|
|
echo
|
|
echo "auto ${br}"
|
|
if [ "$mode" = "dhcp" ]; then
|
|
echo "iface ${br} inet dhcp"
|
|
else
|
|
echo "iface ${br} inet static"
|
|
echo " address ${cidr}"
|
|
[ -n "$gw" ] && echo " gateway ${gw}"
|
|
fi
|
|
echo " bridge-ports ${uplink}"
|
|
echo " bridge-stp off"
|
|
echo " bridge-fd 0"
|
|
[ "$PVE_NET_VLAN_AWARE" = "true" ] && {
|
|
echo " bridge-vlan-aware yes"
|
|
echo " bridge-vids 2-4094"
|
|
}
|
|
if [ -n "$cidr6" ]; then
|
|
echo
|
|
echo "iface ${br} inet6 static"
|
|
echo " address ${cidr6}"
|
|
[ -n "$gw6" ] && echo " gateway ${gw6}"
|
|
fi
|
|
if [ -n "$PVE_NET_EXTRA" ]; then
|
|
echo
|
|
echo "# PVE_NET_EXTRA"
|
|
printf '%s\n' "$PVE_NET_EXTRA"
|
|
fi
|
|
echo
|
|
echo "source /etc/network/interfaces.d/*"
|
|
} > /etc/network/interfaces
|
|
|
|
# Proxmox'un pvenetcommit.service'i acilista /etc/network/interfaces.new
|
|
# dosyasini interfaces uzerine tasiyor. Kalmis bir .new dosyasi yeni
|
|
# yazdigimiz yapilandirmayi yeniden baslatmada ezer.
|
|
rm -f /etc/network/interfaces.new
|
|
|
|
mark_done network
|
|
|
|
case "$PVE_NET_APPLY" in
|
|
reload)
|
|
log "ifreload -a ile uygulaniyor"
|
|
ip -4 addr flush dev "$uplink" 2>/dev/null || true
|
|
ip -6 addr flush dev "$uplink" scope global 2>/dev/null || true
|
|
if ! ifreload -a; then
|
|
warn "ifreload basarisiz, yedek rota ekleniyor"
|
|
[ -n "$gw" ] && ip route replace default via "$gw" dev "$br" 2>/dev/null || true
|
|
fi
|
|
;;
|
|
reboot)
|
|
log "ag yapilandirmasi icin yeniden baslatiliyor; kalan asamalar sonraki acilista surecek"
|
|
# Bu servis hala calisirken 'systemctl reboot' cagirmak systemd islem
|
|
# kuyrugunda "transaction is destructive" hatasina yol acabilir; bu
|
|
# yuzden reboot'u ayri bir gecici unit uzerinden tetikliyoruz.
|
|
systemd-run --no-block --unit=pve-maas-reboot --on-active=5 \
|
|
/bin/systemctl reboot \
|
|
|| shutdown -r +1 "pve-maas-init: ag yapilandirmasi uygulanacak"
|
|
exit 0
|
|
;;
|
|
none)
|
|
log "PVE_NET_APPLY=none: yapilandirma yazildi, uygulanmadi"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Asama: cluster
|
|
# ---------------------------------------------------------------------------
|
|
peer_fingerprint() {
|
|
local peer="$1"
|
|
openssl s_client -connect "${peer}:8006" -servername "${peer}" </dev/null 2>/dev/null \
|
|
| openssl x509 -noout -fingerprint -sha256 2>/dev/null \
|
|
| cut -d= -f2
|
|
}
|
|
|
|
wait_for_peer() {
|
|
local peer="$1" timeout="$2" i
|
|
log "kume dugumu bekleniyor: ${peer}:8006 (en fazla ${timeout}s)"
|
|
for ((i = 0; i < timeout; i += 5)); do
|
|
if timeout 4 bash -c "exec 3<>/dev/tcp/${peer}/8006" 2>/dev/null; then
|
|
log "${peer}:8006 erisilebilir"
|
|
return 0
|
|
fi
|
|
sleep 5
|
|
done
|
|
return 1
|
|
}
|
|
|
|
cluster_password() {
|
|
if [ -n "$PVE_CLUSTER_PEER_PASSWORD_FILE" ] && [ -r "$PVE_CLUSTER_PEER_PASSWORD_FILE" ]; then
|
|
head -1 "$PVE_CLUSTER_PEER_PASSWORD_FILE"
|
|
else
|
|
printf '%s' "$PVE_CLUSTER_PEER_PASSWORD"
|
|
fi
|
|
}
|
|
|
|
wipe_cluster_secrets() {
|
|
[ "$PVE_CLUSTER_WIPE_SECRETS" = "true" ] || return 0
|
|
local f
|
|
for f in "$CONF_DIR"/conf.d/*.conf; do
|
|
[ -r "$f" ] || continue
|
|
grep -q 'PVE_CLUSTER_PEER_PASSWORD\|PVE_ROOT_PASSWORD' "$f" || continue
|
|
log "kimlik bilgileri temizleniyor: $f"
|
|
sed -i -E "s/^([[:space:]]*(PVE_CLUSTER_PEER_PASSWORD|PVE_ROOT_PASSWORD)(_HASH)?=).*/\1'<silindi>'/" "$f"
|
|
done
|
|
if [ -n "$PVE_CLUSTER_PEER_PASSWORD_FILE" ] && [ -f "$PVE_CLUSTER_PEER_PASSWORD_FILE" ]; then
|
|
shred -u "$PVE_CLUSTER_PEER_PASSWORD_FILE" 2>/dev/null \
|
|
|| rm -f "$PVE_CLUSTER_PEER_PASSWORD_FILE"
|
|
fi
|
|
}
|
|
|
|
wait_for_quorum() {
|
|
local i
|
|
for ((i = 0; i < 180; i += 5)); do
|
|
if pvecm status 2>/dev/null | grep -qE '^Quorate:[[:space:]]+Yes'; then
|
|
return 0
|
|
fi
|
|
sleep 5
|
|
done
|
|
return 1
|
|
}
|
|
|
|
stage_cluster() {
|
|
is_done cluster && return 0
|
|
[ "$PVE_CLUSTER_MODE" = "none" ] && { log "kume islemi yok (PVE_CLUSTER_MODE=none)"; mark_done cluster; return 0; }
|
|
|
|
wait_for_pmxcfs || { warn "/etc/pve baglanmadi, kume asamasi erteleniyor"; return 1; }
|
|
|
|
if [ -f /etc/pve/corosync.conf ]; then
|
|
log "dugum zaten bir kumenin parcasi, kume asamasi atlaniyor"
|
|
mark_done cluster
|
|
return 0
|
|
fi
|
|
|
|
local -a opts=()
|
|
[ -n "$PVE_CLUSTER_LINK0" ] && opts+=(--link0 "$PVE_CLUSTER_LINK0")
|
|
[ -n "$PVE_CLUSTER_LINK1" ] && opts+=(--link1 "$PVE_CLUSTER_LINK1")
|
|
[ -n "$PVE_CLUSTER_NODEID" ] && opts+=(--nodeid "$PVE_CLUSTER_NODEID")
|
|
[ -n "$PVE_CLUSTER_VOTES" ] && opts+=(--votes "$PVE_CLUSTER_VOTES")
|
|
|
|
case "$PVE_CLUSTER_MODE" in
|
|
create)
|
|
[ -n "$PVE_CLUSTER_NAME" ] || { warn "PVE_CLUSTER_NAME bos, kume olusturulamiyor"; return 1; }
|
|
log "kume olusturuluyor: ${PVE_CLUSTER_NAME}"
|
|
if pvecm create "$PVE_CLUSTER_NAME" "${opts[@]}"; then
|
|
wait_for_quorum && log "kume olusturuldu ve quorate"
|
|
mark_done cluster
|
|
else
|
|
warn "pvecm create basarisiz"
|
|
return 1
|
|
fi
|
|
;;
|
|
join)
|
|
[ -n "$PVE_CLUSTER_PEER" ] || { warn "PVE_CLUSTER_PEER bos, katilim yapilamiyor"; return 1; }
|
|
local pw fp
|
|
pw="$(cluster_password)"
|
|
[ -n "$pw" ] || { warn "kume dugumunun root parolasi verilmedi (PVE_CLUSTER_PEER_PASSWORD)"; return 1; }
|
|
|
|
wait_for_peer "$PVE_CLUSTER_PEER" "$PVE_CLUSTER_WAIT" \
|
|
|| { warn "kume dugumu ${PVE_CLUSTER_PEER} zaman asimina ugradi"; return 1; }
|
|
|
|
fp="$PVE_CLUSTER_FINGERPRINT"
|
|
if [ -z "$fp" ] && [ "$PVE_CLUSTER_FINGERPRINT_DISCOVER" = "true" ]; then
|
|
fp="$(peer_fingerprint "$PVE_CLUSTER_PEER")"
|
|
warn "parmak izi otomatik alindi (TOFU, dogrulanmadi): ${fp}"
|
|
fi
|
|
[ -n "$fp" ] || { warn "kume dugumunun sertifika parmak izi alinamadi"; return 1; }
|
|
|
|
local i rc=1
|
|
for ((i = 1; i <= PVE_CLUSTER_RETRIES; i++)); do
|
|
log "kumeye katiliniyor (deneme ${i}/${PVE_CLUSTER_RETRIES}): ${PVE_CLUSTER_PEER}"
|
|
# pvecm add etkilesimli parola sorar; API ucu ile etkilesimsiz calisiyoruz.
|
|
if pvesh create /cluster/config/join \
|
|
--hostname "$PVE_CLUSTER_PEER" \
|
|
--password "$pw" \
|
|
--fingerprint "$fp" \
|
|
"${opts[@]}"; then
|
|
rc=0
|
|
break
|
|
fi
|
|
warn "katilim basarisiz, 30s sonra tekrar denenecek"
|
|
sleep 30
|
|
done
|
|
|
|
if [ "$rc" -ne 0 ]; then
|
|
warn "kumeye katilim ${PVE_CLUSTER_RETRIES} denemede basarisiz"
|
|
return 1
|
|
fi
|
|
|
|
systemctl restart pve-cluster pvedaemon pveproxy pvestatd >/dev/null 2>&1 || true
|
|
if wait_for_quorum; then
|
|
log "kumeye katilim tamamlandi ve quorate"
|
|
else
|
|
warn "katilim yapildi ama quorum saglanamadi, kume durumunu kontrol edin"
|
|
fi
|
|
mark_done cluster
|
|
wipe_cluster_secrets
|
|
;;
|
|
*)
|
|
warn "gecersiz PVE_CLUSTER_MODE=${PVE_CLUSTER_MODE}"
|
|
mark_done cluster
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Asama: storage - LVM-thin havuzu
|
|
# ---------------------------------------------------------------------------
|
|
pick_vg() {
|
|
if [ "$PVE_THINPOOL" != "auto" ] && [ "$PVE_THINPOOL" != "off" ]; then
|
|
echo "$PVE_THINPOOL"; return
|
|
fi
|
|
vgs --noheadings --nosuffix --units b -o vg_name,vg_free 2>/dev/null \
|
|
| awk '{gsub(/^ +/,""); print $2, $1}' | sort -rn | head -1 | awk '{print $2}'
|
|
}
|
|
|
|
stage_storage() {
|
|
is_done storage && return 0
|
|
[ "$PVE_THINPOOL" = "off" ] && { log "thin havuz kapali"; mark_done storage; return 0; }
|
|
|
|
wait_for_pmxcfs || { warn "/etc/pve baglanmadi, storage asamasi erteleniyor"; return 1; }
|
|
command -v lvs >/dev/null 2>&1 || { warn "lvm2 yok"; mark_done storage; return 0; }
|
|
|
|
local vg="$PVE_THINPOOL_VG"
|
|
|
|
if [ -n "$PVE_THINPOOL_DISK" ]; then
|
|
if [ ! -b "$PVE_THINPOOL_DISK" ]; then
|
|
warn "PVE_THINPOOL_DISK=${PVE_THINPOOL_DISK} blok aygiti degil"
|
|
mark_done storage; return 0
|
|
fi
|
|
if ! pvs "$PVE_THINPOOL_DISK" >/dev/null 2>&1; then
|
|
log "PV/VG olusturuluyor: ${PVE_THINPOOL_DISK} -> ${vg}"
|
|
pvcreate -ff -y "$PVE_THINPOOL_DISK" || { warn "pvcreate basarisiz"; mark_done storage; return 0; }
|
|
vgcreate "$vg" "$PVE_THINPOOL_DISK" || { warn "vgcreate basarisiz"; mark_done storage; return 0; }
|
|
fi
|
|
else
|
|
vg="$(pick_vg)"
|
|
fi
|
|
|
|
[ -n "$vg" ] || { log "uygun VG bulunamadi, thin havuz atlaniyor"; mark_done storage; return 0; }
|
|
|
|
local pool="$PVE_THINPOOL_NAME"
|
|
if lvs "${vg}/${pool}" >/dev/null 2>&1; then
|
|
log "${vg}/${pool} zaten var"
|
|
else
|
|
local free_b free_g
|
|
free_b="$(vgs --noheadings --nosuffix --units b -o vg_free "$vg" 2>/dev/null | tr -d ' ')"
|
|
free_g=$(( ${free_b:-0} / 1024 / 1024 / 1024 ))
|
|
if [ "$free_g" -lt "$PVE_THINPOOL_MIN_GB" ]; then
|
|
log "${vg} icinde yalnizca ${free_g}GiB bos alan var (gereken ${PVE_THINPOOL_MIN_GB}GiB), thin havuz atlaniyor"
|
|
mark_done storage
|
|
return 0
|
|
fi
|
|
log "thin havuz olusturuluyor: ${vg}/${pool} (~${free_g}GiB)"
|
|
if ! lvcreate --type thin-pool -l 95%FREE -n "$pool" "$vg"; then
|
|
warn "lvcreate basarisiz"
|
|
mark_done storage
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
local storage="$PVE_THINPOOL_STORAGE" node
|
|
node="$(hostname -s)"
|
|
if pvesm status --storage "$storage" >/dev/null 2>&1; then
|
|
log "storage '${storage}' zaten tanimli"
|
|
local nodes
|
|
nodes="$(awk -v s="$storage" '$1=="lvmthin:" && $2==s {f=1; next} /^[a-z]+:/{f=0} f && $1=="nodes"{print $2}' /etc/pve/storage.cfg 2>/dev/null)"
|
|
if [ -n "$nodes" ] && ! echo ",$nodes," | grep -q ",${node},"; then
|
|
log "'${storage}' dugum listesine ${node} ekleniyor"
|
|
pvesm set "$storage" --nodes "${nodes},${node}" || warn "pvesm set basarisiz"
|
|
fi
|
|
else
|
|
log "storage tanimlaniyor: ${storage} (lvmthin ${vg}/${pool})"
|
|
local -a sopts=(--vgname "$vg" --thinpool "$pool" --content images,rootdir)
|
|
[ -f /etc/pve/corosync.conf ] && sopts+=(--nodes "$node")
|
|
pvesm add lvmthin "$storage" "${sopts[@]}" || warn "pvesm add basarisiz"
|
|
fi
|
|
|
|
mark_done storage
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
main() {
|
|
load_config
|
|
|
|
if [ "$PVE_ENABLED" != "true" ]; then
|
|
log "PVE_ENABLED=false, hicbir sey yapilmiyor"
|
|
exit 0
|
|
fi
|
|
|
|
log "baslatiliyor (hostname=$(hostname -s))"
|
|
|
|
wait_for_cloud_init
|
|
load_config # cloud-init conf.d dosyalarini yeni yazmis olabilir
|
|
|
|
if ! wait_for_network; then
|
|
warn "yonetim IP'si bulunamadi; sonraki acilista tekrar denenecek"
|
|
exit 1
|
|
fi
|
|
|
|
local failed=0
|
|
stage_hosts || failed=1
|
|
stage_identity || failed=1
|
|
stage_rootpw || failed=1
|
|
stage_network || failed=1
|
|
stage_cluster || failed=1
|
|
stage_storage || failed=1
|
|
|
|
if [ "$failed" -eq 0 ] \
|
|
&& is_done hosts && is_done identity && is_done rootpw \
|
|
&& is_done network && is_done cluster && is_done storage; then
|
|
date -Is > "$STATE_DIR/complete"
|
|
log "tum asamalar tamamlandi"
|
|
else
|
|
warn "bazi asamalar tamamlanmadi; servis sonraki acilista tekrar denenecek"
|
|
warn "ayrintilar: journalctl -u pve-maas-init"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
main "$@"
|