#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export DEBIAN_FRONTEND=noninteractive

MASTER_URL="${MASTER_URL:-https://api.voxi.club}"
RELEASE_BASE="https://dl.voxi.club/worker"
VOXI_DIR=/opt/voxi
SAVED_BUNDLE=$VOXI_DIR/enrollment.json
COMPOSE=(docker compose --project-directory "$VOXI_DIR" -f "$VOXI_DIR/docker-compose.yml")

usage() {
    cat <<'EOF'
Voxi worker kurulumu

  Voxi worker'ı (panelden alınan jeton):
    curl -fsSL https://dl.voxi.club/install-worker.sh | sudo bash -s -- <JETON> [seçenekler]

  Kurumsal sunucu (lisans dosyası):
    curl -fsSL https://dl.voxi.club/install-worker.sh -o install-worker.sh
    sudo bash install-worker.sh --license ./license.dat [seçenekler]

  Kurulu bir sunucuda aynı lisans komutu yalnız lisansı yeniler: önce veritabanı ve
  ayarlar yedeklenir, lisans değiştirilir ve worker yeniden başlatılır. Worker yeni
  lisansla açılmazsa önceki lisansa dönülür.

Seçenekler:
  --license <dosya>        Kurumsal lisans dosyası (license.dat)
  --tls <yöntem>           auto   Let's Encrypt (varsayılan); 80/TCP internetten erişilebilir olmalı
                           custom Kendi sertifikanız (--cert ve --key)
                           proxy  TLS önünüzdeki vekil sunucu ya da yük dengeleyicide sonlanır
  --email <adres>          Let's Encrypt iletişim adresi. Varsayılan: kurumsalda lisans
                           sahibinin, Voxi worker'ında jetonu üreten yöneticinin hesap e-postası
  --cert <dosya>           Sertifika zinciri, PEM (proxy yönteminde isteğe bağlı kaynak sertifikası)
  --key <dosya>            Parolasız özel anahtar, PEM
  --trusted-proxy <CIDR>   Vekil sunucu adresi; günlüklerde gerçek istemci adresi için (tekrarlanabilir)
  --turn-host <ad|IP>      TURN adresi (proxy yönteminde varsayılan: sunucunun genel IP adresi)
  --reinstall              Kurulu sunucuyu onarır: önce doğrulanmış tam yedek alınır; veriler
                           ve worker ayarları korunur
  --backup-dir <dizin>     Yeniden kurulum yedeğinin yazılacağı dizin (ör. ayrı bir disk)
  --resume                 Yarıda kalan kurulumu sürdürür
  --shared-host            Mevcut nginx ile küçük test worker'ı; diğer siteler korunur,
                           worker servislerinin CPU/bellek kullanımı sınırlandırılır
  -h, --help               Bu yardımı gösterir

Kurulumdan sonra: sudo voxi-tls status · sudo /opt/voxi/backup.sh --status
EOF
}

say() { printf '%s\n' "$*"; }
warn() { printf '⚠️  %s\n' "$*" >&2; }
die() {
    printf '❌ %s\n' "$*" >&2
    if [ -n "${RESUME_HINT:-}" ]; then printf '   %s\n' "$RESUME_HINT" >&2; fi
    exit 1
}
on_error() {
    local code=$?
    trap - ERR
    printf '\n❌ İşlem tamamlanamadı (satır %s).\n' "$1" >&2
    if [ -n "${RESUME_HINT:-}" ]; then printf '   %s\n' "$RESUME_HINT" >&2; fi
    exit "$code"
}
trap 'on_error "$LINENO"' ERR

TOKEN="" LICENSE_FILE="" TLS_MODE="" ACME_EMAIL="" TLS_CERT="" TLS_KEY="" TURN_HOST="" BACKUP_DEST=""
RESUME=0 REINSTALL=0 RESUME_HINT="" SHARED_HOST=0
[ ! -f /etc/voxi/shared-host ] || SHARED_HOST=1
PROXIES=()

set_opt() {
    case "$1" in
        --license) LICENSE_FILE=$2 ;;
        --tls) TLS_MODE=$2 ;;
        --email) ACME_EMAIL=$2 ;;
        --cert) TLS_CERT=$2 ;;
        --key) TLS_KEY=$2 ;;
        --trusted-proxy) PROXIES+=("$2") ;;
        --turn-host) TURN_HOST=$2 ;;
        --backup-dir) BACKUP_DEST=$2 ;;
        *) die "Bilinmeyen seçenek: $1 (yardım: --help)" ;;
    esac
}

while [ $# -gt 0 ]; do
    case "$1" in
        --license|--tls|--email|--cert|--key|--trusted-proxy|--turn-host|--backup-dir)
            if [ -z "${2:-}" ] || [ "${2#-}" != "$2" ]; then die "$1 bir değer ister (yardım: --help)"; fi
            set_opt "$1" "$2"
            shift 2 ;;
        --*=*) set_opt "${1%%=*}" "${1#*=}"; shift ;;
        --resume) RESUME=1; shift ;;
        --reinstall) REINSTALL=1; shift ;;
        --shared-host) SHARED_HOST=1; shift ;;
        -h|--help) usage; exit 0 ;;
        vxw_*)
            [ -z "$TOKEN" ] || die "Birden fazla jeton verildi."
            TOKEN=$1
            shift ;;
        *) die "Bilinmeyen seçenek: $1 (yardım: --help)" ;;
    esac
done

case "$TLS_MODE" in ""|auto|custom|proxy) ;; *) die "--tls yalnız auto, custom ya da proxy olabilir." ;; esac
if [ -n "$TLS_CERT" ] || [ -n "$TLS_KEY" ]; then
    [ -n "$TLS_CERT" ] && [ -n "$TLS_KEY" ] || die "--cert ve --key birlikte verilmeli."
    [ -n "$TLS_MODE" ] || TLS_MODE=custom
    [ "$TLS_MODE" != auto ] || die "--cert ve --key yalnız custom ya da proxy yöntemiyle kullanılır."
    for f in "$TLS_CERT" "$TLS_KEY"; do [ -r "$f" ] || die "Dosya okunamıyor: $f"; done
    TLS_CERT=$(readlink -f "$TLS_CERT")
    TLS_KEY=$(readlink -f "$TLS_KEY")
fi
[ "$TLS_MODE" != custom ] || [ -n "$TLS_CERT" ] || die "--tls custom için --cert ve --key gerekli."
if [ -n "$ACME_EMAIL" ]; then
    [ -z "$TLS_MODE" ] || [ "$TLS_MODE" = auto ] || die "--email yalnız Let's Encrypt (auto) ile kullanılır."
    [[ $ACME_EMAIL =~ ^[^@[:space:]]+@[^@[:space:]]+\.[^@[:space:]]+$ ]] || die "Geçersiz e-posta adresi: $ACME_EMAIL"
fi
for p in "${PROXIES[@]}"; do
    [ "$TLS_MODE" = proxy ] || die "--trusted-proxy yalnız --tls proxy ile kullanılır."
    [[ $p =~ ^[0-9A-Fa-f:.]+(/[0-9]{1,3})?$ ]] || die "Geçersiz vekil adresi: $p"
done
if [ -n "$TURN_HOST" ] && [[ ! $TURN_HOST =~ ^[A-Za-z0-9.:-]+$ ]]; then die "Geçersiz TURN adresi: $TURN_HOST"; fi
[ -z "$TOKEN" ] || [ -z "$LICENSE_FILE" ] || die "Jeton ve lisans dosyası birlikte verilemez."
[ -z "$LICENSE_FILE" ] || [ -s "$LICENSE_FILE" ] || die "Lisans dosyası bulunamadı ya da boş: $LICENSE_FILE"
[ "$RESUME" = 0 ] || [ "$REINSTALL" = 0 ] || die "--resume ve --reinstall birlikte kullanılamaz."
if [ -n "$BACKUP_DEST" ]; then
    [ "$REINSTALL" = 1 ] || die "--backup-dir yalnız --reinstall ile kullanılır."
    case "$BACKUP_DEST" in /?*) ;; *) die "--backup-dir mutlak bir yol olmalı." ;; esac
fi

[ "$(id -u)" -eq 0 ] || die "Root yetkisi gerekli; komutu sudo ile çalıştırın."
[ "$(uname -s)" = Linux ] || die "Yalnız Linux sunucular desteklenir."
[ "$(uname -m)" = x86_64 ] || die "Yalnız x86_64 (amd64) sunucular desteklenir; bu sunucu: $(uname -m)."
[ -d /run/systemd/system ] || die "systemd ile çalışan bir sistem gerekli."
[ -r /etc/os-release ] || die "İşletim sistemi tanınamadı."
. /etc/os-release
case "${ID:-}" in
    ubuntu) DOCKER_DIST=ubuntu; DOCKER_CODENAME="${VERSION_CODENAME:-}" ;;
    debian) DOCKER_DIST=debian; DOCKER_CODENAME="${VERSION_CODENAME:-}" ;;
    pardus)
        DOCKER_DIST=debian
        case "${VERSION_ID:-}" in 21*) DOCKER_CODENAME=bullseye ;; 23*) DOCKER_CODENAME=bookworm ;; *) DOCKER_CODENAME=trixie ;; esac ;;
    *)
        if printf '%s' "${ID_LIKE:-}" | grep -qw ubuntu; then
            DOCKER_DIST=ubuntu; DOCKER_CODENAME="${UBUNTU_CODENAME:-${VERSION_CODENAME:-}}"
        elif printf '%s' "${ID_LIKE:-}" | grep -qw debian; then
            DOCKER_DIST=debian; DOCKER_CODENAME="${DEBIAN_CODENAME:-${VERSION_CODENAME:-}}"
        else
            die "Desteklenmeyen dağıtım: ${PRETTY_NAME:-${ID:-?}}. Ubuntu 22.04/24.04, Debian 12 ya da Pardus 23 kullanın."
        fi ;;
esac

iprop() {
    [ -r "$VOXI_DIR/worker.properties" ] || return 0
    sed -n "s|^$1=||p" "$VOXI_DIR/worker.properties" | head -n1 | tr -d '\r'
}
installed_domain() {
    local d
    d=$(iprop worker.domain)
    d=${d#https://}
    printf '%s' "${d%%/*}"
}
saved_tls_mode() {
    [ -r /etc/voxi/tls.conf ] || return 0
    sed -n 's/^mode=//p' /etc/voxi/tls.conf | head -n1
}
installed() { [ -e "$VOXI_DIR/.env" ] || [ -e "$VOXI_DIR/worker.properties" ] || [ -e "$VOXI_DIR/docker-compose.yml" ]; }
orphan_volumes() {
    command -v docker >/dev/null 2>&1 || return 1
    docker volume ls -q 2>/dev/null | grep -qxE 'voxi_(pg_data|object_data)'
}

if [ "$RESUME" = 1 ] || [ -s "$SAVED_BUNDLE" ]; then
    [ -s "$SAVED_BUNDLE" ] || die "Sürdürülecek yarım kurulum yok."
    [ "$REINSTALL" = 0 ] || die "Yarıda kalmış bir kurulum var; önce --reinstall olmadan tamamlayın."
    MODE=resume
elif installed; then
    [ -z "$TOKEN" ] || die "Bu sunucuda zaten bir Voxi worker kurulu; jetonla ikinci bir worker kurulamaz. Lisans yenilemek için --license, onarmak için --reinstall kullanın."
    if [ "$REINSTALL" = 1 ]; then
        MODE=reinstall
    elif [ -n "$LICENSE_FILE" ]; then
        MODE=update
    else
        die "Bu sunucuda zaten bir Voxi worker kurulu. Lisans yenilemek için: sudo bash install-worker.sh --license ./license.dat · Onarmak için: --reinstall (önce tam yedek alınır)."
    fi
elif orphan_volumes; then
    die "Bu sunucuda önceki bir Voxi kurulumuna ait veri birimleri var, ancak ayarları ($VOXI_DIR) yok. Verilerin kaybolmaması için kurulum durdu. Önceki $VOXI_DIR/.env dosyasını yedekten geri koyup --reinstall ile çalıştırın."
else
    [ "$REINSTALL" = 0 ] || die "--reinstall için kurulu bir worker bulunamadı."
    MODE=fresh
    [ -n "$TOKEN$LICENSE_FILE" ] || die "Kayıt jetonu ya da lisans dosyası gerekli (yardım: --help)."
fi
if [ "$MODE" != fresh ] && [ "$MODE" != update ] && [ -f "$VOXI_DIR/worker.properties" ] \
    && ! grep -q '^minio.endpoint=127.0.0.1:9000$' "$VOXI_DIR/worker.properties"; then
    die "Bu kurulum önce doğrulanmış yerel depolama göçünü gerektiriyor; Voxi destek ekibiyle iletişime geçin."
fi
if [ -n "$LICENSE_FILE" ] || [ "$(iprop worker.type)" = enterprise ]; then
    say "ℹ  Kurumsal worker: topluluk verileri ve yedekler yalnız bu sunucuda tutulur; yönetimleri size aittir."
fi

check_resources() {
    local mem_mb free_gb d
    mem_mb=$(awk '/MemTotal/ {print int($2 / 1024)}' /proc/meminfo)
    [ "$mem_mb" -ge 1800 ] || die "En az 2 GB bellek gerekli (bu sunucu: ${mem_mb} MB)."
    [ "$mem_mb" -ge 7500 ] || warn "Bellek ${mem_mb} MB; önerilen 8 GB."
    for d in /opt /var/lib; do
        free_gb=$(df -Pk "$d" | awk 'NR==2 {print int($4 / 1048576)}')
        [ "$free_gb" -ge 15 ] || die "$d için en az 15 GB boş disk gerekli (boş: ${free_gb} GB)."
    done
    say "   sistem: ${PRETTY_NAME:-$ID} · $(nproc) çekirdek · ${mem_mb} MB bellek"
}

listening_ports() {
    local proto file laddr st
    for proto in tcp udp; do
        for file in "/proc/net/$proto" "/proc/net/${proto}6"; do
            [ -r "$file" ] || continue
            while read -r _ laddr _ st _; do
                if [ "$proto" = tcp ] && [ "$st" != 0A ]; then continue; fi
                printf '%s %d\n' "$proto" "0x${laddr##*:}"
            done < <(tail -n +2 "$file")
        done
    done | sort -u
}

check_host_is_free() {
    local busy p conflicts=() sites=() f
    busy=$(listening_ports)
    for p in tcp:80 tcp:443 tcp:3478 tcp:5432 tcp:6379 tcp:8090 tcp:9000 udp:3478; do
        if [ "$SHARED_HOST" = 1 ] && { [ "$p" = tcp:80 ] || [ "$p" = tcp:443 ]; }; then
            continue
        fi
        if grep -qx "${p%%:*} ${p##*:}" <<< "$busy"; then conflicts+=("${p##*:}/${p%%:*}"); fi
    done
    [ "${#conflicts[@]}" -eq 0 ] || die "Gerekli portlar başka bir uygulama tarafından kullanılıyor: ${conflicts[*]}. O uygulamaları durdurun ya da worker'ı ayrı, boş bir sunucuya kurun (kullanan süreçler: sudo ss -ltnup)."
    for f in /etc/nginx/sites-enabled/* /etc/nginx/conf.d/*.conf; do
        [ -e "$f" ] || continue
        case "${f##*/}" in default|voxi-worker) ;; *) sites+=("$f") ;; esac
    done
    if [ "$SHARED_HOST" = 1 ]; then
        command -v nginx >/dev/null && systemctl is-active --quiet nginx && nginx -t \
            || die "--shared-host için çalışan ve geçerli yapılandırmalı nginx gerekli."
        [ ! -e /etc/nginx/sites-available/voxi-worker ] && [ ! -e /etc/turnserver.conf ] \
            || die "Paylaşımlı sunucuda eski worker/TURN yapılandırması var; üzerine yazılmayacak."
        [ "$(awk '/MemAvailable/{print int($2/1024)}' /proc/meminfo)" -ge 800 ] \
            || die "Paylaşımlı kurulum için en az 800 MiB kullanılabilir bellek gerekli."
    else
        [ "${#sites[@]}" -eq 0 ] || die "Bu sunucuda başka web siteleri tanımlı (${sites[*]}). Worker'ı ayrı, boş bir sunucuya kurun."
    fi
}

check_cert_pair() {
    local cfp kfp
    openssl x509 -in "$TLS_CERT" -noout >/dev/null 2>&1 || die "Sertifika okunamadı: $TLS_CERT"
    openssl pkey -in "$TLS_KEY" -passin pass: -noout >/dev/null 2>&1 || die "Özel anahtar okunamadı (PEM ve parolasız olmalı): $TLS_KEY"
    cfp=$(openssl x509 -in "$TLS_CERT" -noout -pubkey | openssl pkey -pubin -outform DER | sha256sum)
    kfp=$(openssl pkey -in "$TLS_KEY" -passin pass: -pubout | openssl pkey -pubin -outform DER | sha256sum)
    [ "$cfp" = "$kfp" ] || die "Özel anahtar sertifikayla eşleşmiyor."
    openssl x509 -in "$TLS_CERT" -noout -checkend 0 >/dev/null 2>&1 || die "Sertifikanın süresi dolmuş."
}

ensure_tools() {
    local t missing=0
    for t in curl jq python3 sudo openssl; do command -v "$t" >/dev/null 2>&1 || missing=1; done
    [ -d /etc/ssl/certs ] || missing=1
    [ "$missing" = 1 ] || return 0
    say "→ gerekli araçlar kuruluyor"
    apt-get update -q >/dev/null
    apt-get install -yq curl jq python3 ca-certificates openssl sudo >/dev/null
}

ensure_docker() {
    command -v docker >/dev/null 2>&1 && return 0
    say "→ Docker kuruluyor ($DOCKER_DIST/$DOCKER_CODENAME)"
    apt-get update -q >/dev/null
    apt-get install -yq gnupg ca-certificates curl >/dev/null
    install -m 0755 -d /etc/apt/keyrings
    curl -fsSL "https://download.docker.com/linux/$DOCKER_DIST/gpg" | gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg
    chmod 644 /etc/apt/keyrings/docker.gpg
    echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$DOCKER_DIST $DOCKER_CODENAME stable" \
        > /etc/apt/sources.list.d/docker.list
    chmod 644 /etc/apt/sources.list.d/docker.list
    apt-get update -q >/dev/null
    apt-get install -yq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin >/dev/null
}

fetch_release() {
    mkdir -p "$VOXI_DIR/keys" "$VOXI_DIR/releases"
    STAGE=$(mktemp -d "$VOXI_DIR/releases/install.XXXXXX")
    trap 'rm -rf "$STAGE"' EXIT
    say "→ imzalı kurulum paketi indiriliyor"
    curl --proto '=https' -fsS "$RELEASE_BASE/bootstrap/linux-amd64/voxi-update-verify" -o "$STAGE/verify" \
        || die "Doğrulayıcı indirilemedi; dl.voxi.club erişimini kontrol edin."
    curl --proto '=https' -fsS "$RELEASE_BASE/bootstrap/worker_release_public.pem" -o "$STAGE/public.pem" \
        || die "Yayın anahtarı indirilemedi; dl.voxi.club erişimini kontrol edin."
    chmod 700 "$STAGE/verify"
    if [ -s "$VOXI_DIR/keys/worker_release_public.pem" ]; then
        if ! cmp -s "$STAGE/public.pem" "$VOXI_DIR/keys/worker_release_public.pem"; then
            "$STAGE/verify" -key "$VOXI_DIR/keys/worker_release_public.pem" -keyset-overlap "$STAGE/public.pem" \
                || die "Yayın anahtarı bu sunucunun güvendiği anahtarla bağlantısız; paket reddedildi."
            install -m 644 "$STAGE/public.pem" "$VOXI_DIR/keys/worker_release_public.pem"
        fi
    else
        install -m 644 "$STAGE/public.pem" "$VOXI_DIR/keys/worker_release_public.pem"
    fi
    fetch_signed "$RELEASE_BASE/stable/installer.json" binary "$STAGE/installer.tar.gz"
    mkdir "$STAGE/bundle"
    tar -xzf "$STAGE/installer.tar.gz" -C "$STAGE/bundle" --no-same-owner
    fetch_signed "$RELEASE_BASE/stable/manifest.json" docker "$STAGE/image.tar.gz"
    RELEASE_IMAGE=$(jq -er '.artifact.image+":"+.artifact.tag' "$STAGE/verified.json")
    SETUP="$STAGE/bundle/deploy/setup-worker.sh"
    NEW_SETUP=0
    if grep -q 'VOXI_TLS_MODE' "$SETUP"; then NEW_SETUP=1; fi
}

fetch_signed() {
    local manifest="$1" kind="$2" output="$3"
    curl --proto '=https' -fsS "$manifest" -o "$STAGE/manifest.json" || die "Yayın bilgisi indirilemedi: $manifest"
    "$STAGE/verify" -key "$VOXI_DIR/keys/worker_release_public.pem" -manifest "$STAGE/manifest.json" -kind "$kind" > "$STAGE/verified.json" \
        || die "Yayın imzası doğrulanamadı; paket reddedildi."
    curl --proto '=https' -fsS --max-filesize 536870912 "$(jq -er '.artifact.url' "$STAGE/verified.json")" -o "$output" \
        || die "Paket indirilemedi."
    [ "$(stat -c %s "$output")" = "$(jq -er '.artifact.size' "$STAGE/verified.json")" ] || die "İndirilen paketin boyutu imzalı değerle uyuşmuyor."
    printf '%s  %s\n' "$(jq -er '.artifact.sha256' "$STAGE/verified.json")" "$output" | sha256sum -c - >/dev/null 2>&1 \
        || die "İndirilen paketin SHA-256 özeti imzalı değerle uyuşmuyor."
}

enroll() {
    local url body out code msg
    body=$(mktemp)
    out=$(mktemp)
    if [ -n "$LICENSE_FILE" ]; then
        url="$MASTER_URL/enroll/enterprise"
        jq -n --rawfile lic "$LICENSE_FILE" '{license: ($lic | fromjson? // $lic)}' > "$body"
    else
        url="$MASTER_URL/enroll/worker"
        jq -n --arg t "$TOKEN" '{token: $t}' > "$body"
    fi
    say "→ Voxi'den yapılandırma alınıyor"
    code=$(curl -sS -o "$out" -w '%{http_code}' --max-time 30 -X POST "$url" \
        -H 'Content-Type: application/json' --data-binary "@$body" || echo 000)
    rm -f "$body"
    if [ "$code" != 200 ]; then
        msg=$(jq -r '(.error.message? // .message? // (.error | strings?)) // empty' < "$out" 2>/dev/null || true)
        rm -f "$out"
        die "Kayıt başarısız (HTTP $code): ${msg:-sunucuya ulaşılamadı}"
    fi
    CFG=$(jq -c 'if has("data") then .data else . end' < "$out" 2>/dev/null) || { rm -f "$out"; die "Voxi'den geçersiz yanıt alındı."; }
    rm -f "$out"
}

cfg() { jq -r ".$1 // empty" <<< "$CFG"; }

check_identity() {
    local slug domain
    slug=$(cfg slug)
    domain=$(cfg domain)
    if [ "$slug" != "$(iprop worker.slug)" ] || [ "$domain" != "$(installed_domain)" ]; then
        die "Bu sunucu $(installed_domain) için kurulu; lisans $domain için. Farklı bir worker mevcut verinin üzerine kurulamaz."
    fi
}

write_license() {
    mkdir -p "$VOXI_DIR/keys"
    printf '%s\n' "$1" > "$VOXI_DIR/license.dat"
    printf '%s\n' "$2" > "$VOXI_DIR/keys/license_public.pem"
    chmod 640 "$VOXI_DIR/license.dat" "$VOXI_DIR/keys/license_public.pem"
    chown 100:101 "$VOXI_DIR/license.dat" "$VOXI_DIR/keys/license_public.pem" 2>/dev/null || true
}

wait_healthy() {
    local deadline=$((SECONDS + 180)) streak=0
    sleep 5
    while [ "$SECONDS" -lt "$deadline" ]; do
        if curl -fsS --max-time 5 http://127.0.0.1:8090/health/ready 2>/dev/null | jq -e '.status == "ok"' >/dev/null 2>&1; then
            streak=$((streak + 1))
            [ "$streak" -lt 3 ] || return 0
        else
            streak=0
        fi
        sleep 5
    done
    return 1
}

lock_updater() {
    [ -d /var/lib/voxi-worker-updater ] || return 0
    exec 8>/var/lib/voxi-worker-updater/lock
    flock -w 300 8 || die "Otomatik güncelleme sürüyor; birkaç dakika sonra tekrar deneyin."
}

run_update() {
    local lic pub
    ensure_tools
    enroll
    check_identity
    lic=$(jq -r '.license_data // empty' <<< "$CFG")
    pub=$(jq -r '.license_public_key // empty' <<< "$CFG")
    [ -n "$lic" ] && [ -n "$pub" ] || die "Lisans belgesi alınamadı; kurulum değiştirilmedi."
    if [ "$lic" = "$(cat "$VOXI_DIR/license.dat" 2>/dev/null)" ] && [ "$pub" = "$(cat "$VOXI_DIR/keys/license_public.pem" 2>/dev/null)" ]; then
        say "✓ Lisans zaten güncel; değişiklik yapılmadı."
        return 0
    fi
    [ -x "$VOXI_DIR/backup.sh" ] || die "Yedekleme aracı bulunamadı ($VOXI_DIR/backup.sh); lisans değiştirilmedi."
    say "→ lisans değiştirilmeden önce veritabanı ve ayarlar yedekleniyor"
    "$VOXI_DIR/backup.sh" --pre-update || die "Yedek alınamadı; lisans değiştirilmedi."
    lock_updater
    cp -p "$VOXI_DIR/license.dat" "$VOXI_DIR/license.dat.voxi-prev"
    cp -p "$VOXI_DIR/keys/license_public.pem" "$VOXI_DIR/keys/license_public.pem.voxi-prev"
    write_license "$lic" "$pub"
    say "→ worker yeni lisansla yeniden başlatılıyor"
    "${COMPOSE[@]}" up -d --no-deps --force-recreate worker >/dev/null 2>&1 || true
    if wait_healthy; then
        rm -f "$VOXI_DIR/license.dat.voxi-prev" "$VOXI_DIR/keys/license_public.pem.voxi-prev"
        say "✓ Lisans güncellendi."
        return 0
    fi
    write_license "$(cat "$VOXI_DIR/license.dat.voxi-prev")" "$(cat "$VOXI_DIR/keys/license_public.pem.voxi-prev")"
    "${COMPOSE[@]}" up -d --no-deps --force-recreate worker >/dev/null 2>&1 || true
    rm -f "$VOXI_DIR/license.dat.voxi-prev" "$VOXI_DIR/keys/license_public.pem.voxi-prev"
    die "Worker yeni lisansla açılmadı; önceki lisansa dönüldü. Günlükler: sudo docker compose -f $VOXI_DIR/docker-compose.yml logs --tail 100 worker"
}

check_dns() {
    local ips seen
    ips=$(getent ahostsv4 "$WORKER_DOMAIN" 2>/dev/null | awk '{print $1}' | sort -u | tr '\n' ' ' || true)
    seen=$(curl -fsS --max-time 5 https://checkip.amazonaws.com 2>/dev/null | tr -d '[:space:]' || true)
    if [ -z "$ips" ]; then
        [ "$EFFECTIVE_TLS" != auto ] || die "$WORKER_DOMAIN çözümlenemiyor. DNS A kaydını bu sunucuya (${seen:-genel IP}) yönlendirip aynı komutu tekrar çalıştırın."
        warn "$WORKER_DOMAIN henüz çözümlenmiyor; istemcilerin bağlanabilmesi için DNS kaydı gerekli."
    elif [ "$EFFECTIVE_TLS" != proxy ] && [ -n "$seen" ] && ! grep -qwF "$seen" <<< "$ips"; then
        warn "$WORKER_DOMAIN → $ips, bu sunucu → $seen. Yük dengeleyici kullanmıyorsanız DNS kaydını düzeltin."
    fi
}

prepare_env() {
    local env_file="$VOXI_DIR/.env" local_access local_secret secret
    touch "$env_file"
    chmod 600 "$env_file"
    ensure_env() { grep -qE "^$1=" "$env_file" || printf '%s=%s\n' "$1" "$2" >> "$env_file"; }
    set_env() {
        if grep -qE "^$1=" "$env_file"; then sed -i -E "s|^$1=.*|$1=$2|" "$env_file"; else printf '%s=%s\n' "$1" "$2" >> "$env_file"; fi
    }
    ensure_env REDIS_PASS "$(openssl rand -hex 24)"
    ensure_env TURN_USER voxi
    ensure_env TURN_PASS "$(openssl rand -hex 24)"
    secret=$(cfg internal_secret)
    if [ -n "$secret" ]; then set_env MASTER_INTERNAL_SECRET "$secret"; fi
    ensure_env LOCAL_STORAGE_ACCESS_KEY "$(openssl rand -hex 12)"
    ensure_env LOCAL_STORAGE_SECRET_KEY "$(openssl rand -hex 32)"
    local_access=$(sed -n 's/^LOCAL_STORAGE_ACCESS_KEY=//p' "$env_file")
    local_secret=$(sed -n 's/^LOCAL_STORAGE_SECRET_KEY=//p' "$env_file")
    set_env MINIO_ACCESS_KEY "$local_access"
    set_env MINIO_SECRET_KEY "$local_secret"
    if [ "$(cfg data_mode)" = isolated ]; then
        ensure_env LOCAL_DB_USER voxi
        ensure_env LOCAL_DB_NAME voxi_data
        ensure_env LOCAL_DB_PASS "$(openssl rand -hex 24)"
        ensure_env COMPOSE_PROFILES isolated
    fi
}

tls_env() {
    [ "$NEW_SETUP" = 1 ] || return 0
    export VOXI_TLS_MODE="$TLS_MODE"
    if [ "$EFFECTIVE_TLS" = auto ] && [ -n "$ACME_EMAIL" ]; then export VOXI_ACME_EMAIL="$ACME_EMAIL"; fi
    if [ -n "$TLS_CERT" ]; then export VOXI_TLS_CERT="$TLS_CERT" VOXI_TLS_KEY="$TLS_KEY"; fi
    if [ "${#PROXIES[@]}" -gt 0 ]; then export VOXI_TLS_TRUSTED_PROXIES="${PROXIES[*]}"; fi
    if [ -n "$TURN_HOST" ]; then export TURN_DOMAIN="$TURN_HOST"; fi
}

open_firewall() {
    local rule
    if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q "Status: active"; then
        say "→ güvenlik duvarı (ufw) portları açılıyor"
        for rule in 80/tcp 443/tcp 3478/tcp 3478/udp 40000:49999/udp; do ufw allow "$rule" >/dev/null; done
    fi
}

run_install() {
    local lic pub
    check_resources
    if [ "$MODE" = fresh ]; then check_host_is_free; fi
    ensure_tools
    if [ -n "$TLS_CERT" ]; then check_cert_pair; fi
    ensure_docker
    fetch_release
    if [ "$SHARED_HOST" = 1 ] && ! grep -q 'VOXI_SHARED_HOST' "$SETUP"; then
        die "Yayındaki kurulum paketi paylaşımlı sunucuyu desteklemiyor; mevcut servisler değiştirilmedi."
    fi
    if [ "$NEW_SETUP" = 0 ] && { { [ -n "$TLS_MODE" ] && [ "$TLS_MODE" != auto ]; } || [ -n "$ACME_EMAIL$TURN_HOST" ] || [ "${#PROXIES[@]}" -gt 0 ]; }; then
        die "Bu TLS seçenekleri yayındaki worker sürümünde henüz yok; seçeneksiz kurun ya da daha sonra tekrar deneyin."
    fi
    if [ "$MODE" = reinstall ] && { ! grep -q -- '--pre-reinstall' "$STAGE/bundle/deploy/backup-worker.sh" || ! grep -q 'VOXI_KEEP_PROPERTIES' "$SETUP"; }; then
        die "Onarım için güncel worker sürümü gerekli; daha sonra tekrar deneyin."
    fi

    CFG="{}"
    case "$MODE" in
        resume)
            if [ -n "$LICENSE_FILE" ]; then enroll; else CFG=$(cat "$SAVED_BUNDLE"); say "   yarım kalan kurulumun yapılandırması kullanılıyor"; fi ;;
        fresh) enroll ;;
        reinstall) if [ -n "$LICENSE_FILE" ]; then enroll; check_identity; fi ;;
    esac
    if [ "$MODE" != reinstall ]; then
        ( umask 077; printf '%s' "$CFG" > "$SAVED_BUNDLE" )
        RESUME_HINT="Sorunu giderip aynı komutu tekrar çalıştırın; kurulum kaldığı yerden sürer (jeton tekrar gerekmez)."
        WORKER_SLUG=$(cfg slug)
        WORKER_DOMAIN=$(cfg domain)
        WORKER_TYPE=$(cfg type)
        DATA_MODE=$(cfg data_mode)
        [ -n "$WORKER_SLUG" ] && [ -n "$WORKER_DOMAIN" ] || die "Voxi'den gelen yapılandırma eksik (slug/alan adı yok)."
        EFFECTIVE_TLS=${TLS_MODE:-auto}
    else
        WORKER_SLUG=$(iprop worker.slug)
        WORKER_DOMAIN=$(installed_domain)
        WORKER_TYPE=$(iprop worker.type)
        DATA_MODE=$(iprop data.mode)
        EFFECTIVE_TLS=${TLS_MODE:-$(saved_tls_mode)}
        EFFECTIVE_TLS=${EFFECTIVE_TLS:-auto}
        RESUME_HINT="Önceki durumun yedeği $VOXI_DIR/backups/pre-reinstall (ya da --backup-dir) altındadır. Sorunu giderip komutu --reinstall ile tekrar çalıştırın."
    fi
    say "   worker: $WORKER_DOMAIN · $WORKER_SLUG · ${WORKER_TYPE:-system}"

    if [ "$MODE" != reinstall ]; then check_dns; fi
    if [ "$EFFECTIVE_TLS" = custom ] && [ -n "$TLS_CERT" ]; then
        grep -q 'does match' <<< "$(openssl x509 -in "$TLS_CERT" -noout -checkhost "$WORKER_DOMAIN" 2>/dev/null || true)" \
            || die "Sertifika $WORKER_DOMAIN alan adını kapsamıyor."
    fi
    if [ "$EFFECTIVE_TLS" = auto ] && [ "$NEW_SETUP" = 1 ] && [ ! -e "/etc/letsencrypt/live/$WORKER_DOMAIN/fullchain.pem" ]; then
        [ -n "$ACME_EMAIL" ] || ACME_EMAIL=$(cfg acme_email)
        if [ -z "$ACME_EMAIL" ] && ! ls /etc/letsencrypt/accounts/*/directory/* >/dev/null 2>&1; then
            die "Let's Encrypt için iletişim e-postası gerekli: --email <adres>"
        fi
        if [ -n "$ACME_EMAIL" ]; then
            say "   Let's Encrypt iletişim adresi: $ACME_EMAIL (değiştirmek için --email)."
            say "   Otomatik sertifika ile Let's Encrypt abonelik sözleşmesini sunucu sahibi olarak kabul etmiş olursunuz."
        fi
    fi

    if [ "$MODE" = reinstall ]; then
        say "→ onarımdan önce tam yedek alınıyor (doğrulanır)"
        bash "$STAGE/bundle/deploy/backup-worker.sh" --pre-reinstall ${BACKUP_DEST:+--dest "$BACKUP_DEST"} \
            || die "Yedek alınamadı; sunucuda değişiklik yapılmadı."
        export VOXI_KEEP_PROPERTIES=1
        export MASTER_API_URL MINIO_ENDPOINT=127.0.0.1:9000 MINIO_BUCKET
        MASTER_API_URL=$(iprop master.api.url)
        MINIO_BUCKET=$(iprop minio.bucket)
    else
        if [ "$MODE" = fresh ] && [ -f /etc/turnserver.conf ] && [ ! -e /etc/turnserver.conf.before-voxi ]; then
            cp -p /etc/turnserver.conf /etc/turnserver.conf.before-voxi
        fi
        prepare_env
        WORKER_REGION=$(cfg region)
        WORKER_MAX_USERS=$(cfg max_concurrent_users)
        WORKER_LISTED=$(jq -r 'if .is_listed == null then "" else (.is_listed | tostring) end' <<< "$CFG")
        MASTER_API_URL=$(cfg master_api_url)
        MINIO_ENDPOINT=$(cfg minio_endpoint)
        MINIO_BUCKET=$(cfg minio_bucket)
        export WORKER_REGION WORKER_MAX_USERS WORKER_LISTED MASTER_API_URL MINIO_ENDPOINT MINIO_BUCKET
        if [ "$WORKER_TYPE" = system ]; then
            export WORKER_DISTRIBUTED_VOICE=true
            sed -i -E 's|^voice\.distributed\.enabled=.*|voice.distributed.enabled=true|' \
                "$STAGE/bundle/deploy/worker.properties.prod.template"
        fi
    fi
    if [ "$WORKER_TYPE" = enterprise ] && [ "$(cfg license_data)" != "" ]; then
        lic=$(cfg license_data)
        pub=$(cfg license_public_key)
        [ -n "$pub" ] || die "Lisans doğrulama anahtarı alınamadı."
        write_license "$lic" "$pub"
    fi
    [ "$WORKER_TYPE" != enterprise ] || [ -s "$VOXI_DIR/license.dat" ] || die "Kurumsal kurulum için lisans belgesi alınamadı."

    tls_env
    if [ "$SHARED_HOST" = 1 ]; then
        install -d -m 755 /etc/voxi
        touch /etc/voxi/shared-host
        export VOXI_SHARED_HOST=1
    fi
    export WORKER_DOMAIN WORKER_SLUG WORKER_TYPE DATA_MODE
    export DEPLOY_USER=root VOXI_INITIAL_ARCHIVE="$STAGE/image.tar.gz" VOXI_INITIAL_IMAGE="$RELEASE_IMAGE"
    say "→ kurulum çalıştırılıyor"
    bash "$SETUP"
    open_firewall
    # PostgreSQL's first initialization can outlive `compose up -d`, especially
    # with shared-host CPU limits. Do not announce success before the app is ready.
    wait_healthy || die "Worker hazır duruma geçmedi; docker compose logs worker worker-postgres ile kontrol edin ve aynı komutla devam edin."
    rm -f "$SAVED_BUNDLE"
    RESUME_HINT=""
}

summary() {
    say ""
    say "═══════════════════════════════════════════════════════════"
    say "✅ Tamamlandı: https://$WORKER_DOMAIN"
    if command -v voxi-tls >/dev/null 2>&1; then
        voxi-tls status 2>/dev/null | sed 's/^/   /' || true
    fi
    if [ -x "$VOXI_DIR/backup.sh" ]; then
        say "   Yedek      : her gün 04:17 UTC → $VOXI_DIR/backups (ayarlar: /etc/voxi/backup.conf)"
    fi
    say "   Doğrulama  : curl -s https://$WORKER_DOMAIN/api/v1/ping"
    say "                sudo docker compose -f $VOXI_DIR/docker-compose.yml ps"
    [ -n "${EFFECTIVE_TLS:-}" ] || EFFECTIVE_TLS=$(saved_tls_mode)
    if [ "${EFFECTIVE_TLS:-auto}" = proxy ]; then
        say "   Vekil      : https://$WORKER_DOMAIN trafiği (WebSocket dahil) bu sunucunun 443/TCP portuna iletilmeli;"
        say "                UDP 3478 ve 40000-49999 doğrudan bu sunucuya ulaşmalı."
    else
        say "   Güvenlik duvarında açık olmalı: TCP 80, 443 · TCP+UDP 3478 · UDP 40000-49999"
    fi
    if [ "${WORKER_TYPE:-}" = enterprise ]; then
        say ""
        say "   Veri sorumluluğu"
        say "   Topluluk verileri (mesajlar, dosyalar, kayıtlar), veritabanı ve yedekler yalnızca bu"
        say "   sunucuda tutulur; yönetimi, korunması ve yedeklerin saklanması tamamen size aittir."
        say "   Voxi bu verilerin kopyasını tutmaz, yedeğini almaz ve veri kaybından sorumlu değildir."
        say "   Yedekleri sunucu dışına almak için /etc/voxi/backup.conf içindeki TARGET ayarını yapın."
    fi
    say "═══════════════════════════════════════════════════════════"
}

say "═══ Voxi worker ═══"
case "$MODE" in
    update)
        [ -z "$TLS_MODE$ACME_EMAIL$TURN_HOST" ] && [ "${#PROXIES[@]}" -eq 0 ] \
            || die "Kurulu sunucuda TLS ayarı için: sudo voxi-tls --help"
        WORKER_DOMAIN=$(installed_domain)
        WORKER_TYPE=$(iprop worker.type)
        run_update ;;
    *) run_install ;;
esac
summary
