#!/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 "$@"
