#!/usr/bin/env bash
# =============================================================================
# onx-cpanel-import — Full cPanel pkgacct import driver
#
# Parses a cpmove-<USER>.tar.gz archive end-to-end and emits a staging dir
# containing typed JSON manifests for Laravel to ingest. By design the script
# never writes to the panel database; it only writes to disk locations the
# rest of the sysapi stack already owns (/home/<user>, /var/lib/mysql via
# `mysql`, /etc/onoxsoft/imported-ssl, /etc/cron.d).
#
# Input (stdin JSON):
#   {
#     "archive_path":    "/home/onx_acme01/migrations/cpmove-acme.tar.gz",  -- required
#     "target_username": "onx_acme01",                                       -- required
#     "mode":            "inspect|plan|execute",                             -- required
#     "skip":            ["homedir","mysql","dns","email","ssl","cron"],     -- optional
#     "dry_run":         false                                                -- optional
#   }
#
# Output (stdout JSON, one object — see footer for full shape):
#   {
#     "source_user":     "acme",
#     "target_user":     "onx_acme01",
#     "pkgacct_version": "11.110",
#     "primary_domain":  "acme.com",
#     "staging_dir":     "/var/lib/onox/migrations/cpanel-XXXX",
#     "mode":            "execute",
#     "dry_run":         false,
#     "skip":            [],
#     "imported": {
#       "homedir_bytes":  483920104,
#       "emails":         12,
#       "forwarders":     3,
#       "databases":      4,
#       "dns_zones":      2,
#       "ssl_certs":      1,
#       "cron_jobs":      5
#     },
#     "manifests": {
#       "emails":      ".../emails.json",
#       "forwarders":  ".../forwarders.json",
#       "databases":   ".../databases.json",
#       "dns_zones":   ".../dns_zones.json",
#       "ssl_certs":   ".../ssl_certs.json",
#       "cron_jobs":   ".../cron_jobs.json"
#     }
#   }
#
# Exit codes: 0 ok / 1 invalid input / 2 preflight / 3 exec
#
# Deployed to: /usr/local/onoxsoft/bin/onx-cpanel-import
# =============================================================================

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_lib/common.sh
source "${SCRIPT_DIR}/_lib/common.sh"

readonly STAGING_ROOT="/var/lib/onox/migrations"
readonly SSL_IMPORT_ROOT="/etc/onoxsoft/imported-ssl"

require_root
require_cmd tar
require_cmd jq

onx_json_input

# ─── Parse input ─────────────────────────────────────────────────────────────
ARCHIVE_PATH="$(onx_json_field archive_path)"
TARGET_USER="$(onx_json_field target_username)"
MODE="$(onx_json_field mode 'inspect')"
DRY_RUN="$(onx_json_get_bool "$INPUT" dry_run 'false')"
SKIP_JSON="$(printf '%s' "$INPUT" | jq -c '.skip // []')"

[[ -z "$ARCHIVE_PATH" ]] && onx_die 1 "archive_path zorunlu"
[[ -z "$MODE" ]] && MODE="inspect"

case "$MODE" in
    inspect|plan|execute) : ;;
    *) onx_die 1 "mode geçersiz (inspect|plan|execute bekleniyor): $MODE" ;;
esac

# Target username only required for plan / execute
if [[ "$MODE" != "inspect" ]]; then
    [[ -z "$TARGET_USER" ]] && onx_die 1 "target_username zorunlu (mode=$MODE)"
    onx_validate_username "$TARGET_USER"
fi

# Resolve & sandbox archive path
ARCHIVE_REAL="$(realpath -e "$ARCHIVE_PATH" 2>/dev/null)" \
    || onx_die 1 "Arşiv bulunamadı: $ARCHIVE_PATH"

case "$ARCHIVE_REAL" in
    /home/*|/var/lib/onox/*|/var/www/*/storage/app/*|/opt/*/storage/app/*) : ;;
    *) onx_die 1 "archive_path güvenli alanda değil: $ARCHIVE_REAL" ;;
esac

[[ -f "$ARCHIVE_REAL" ]] || onx_die 1 "archive_path bir dosya değil: $ARCHIVE_REAL"

# Helper — is_skip section
_is_skip() {
    local section="$1"
    echo "$SKIP_JSON" | jq -e --arg s "$section" 'index($s) != null' >/dev/null 2>&1
}

# ─── Stage directory ─────────────────────────────────────────────────────────
mkdir -p "$STAGING_ROOT"
# 0751: panel (non-root queue worker) staging alt-dizinine TRAVERSE edebilsin (manifest
# okuma); listeleme YOK (güvenli). Üst zincir de traverse edilebilir olsun.
chmod 0751 "$STAGING_ROOT"
chmod o+x "$(dirname "$STAGING_ROOT")" 2>/dev/null || true

STAGING="$(mktemp -d -p "$STAGING_ROOT" cpanel-XXXXXX)"
chmod 0750 "$STAGING"

# Rollback registers staging cleanup if anything fails
trap 'onx_rollback_run' ERR
onx_rollback_register "rm -rf '$STAGING'"

# ─── Extract archive ─────────────────────────────────────────────────────────
EXTRACT_DIR="$STAGING/extracted"
mkdir -p "$EXTRACT_DIR"

onx_log "cpanel-import: extracting $ARCHIVE_REAL → $EXTRACT_DIR"
tar -xzf "$ARCHIVE_REAL" -C "$EXTRACT_DIR" \
    || onx_die 3 "tar extract başarısız: $ARCHIVE_REAL"

# Locate top-level cpmove-<user>/ directory (or use first dir)
SOURCE_ROOT="$(find "$EXTRACT_DIR" -mindepth 1 -maxdepth 1 -type d | head -n1)"
[[ -n "$SOURCE_ROOT" && -d "$SOURCE_ROOT" ]] \
    || onx_die 3 "Beklenen üst dizin bulunamadı (cpmove-<user>/)"

BASENAME="$(basename "$SOURCE_ROOT")"
# cpmove-<user> → <user>;  bazı yaşlı pkgacct'lar direkt <user>/ kullanır.
if [[ "$BASENAME" =~ ^cpmove-(.+)$ ]]; then
    SOURCE_USER="${BASH_REMATCH[1]}"
else
    SOURCE_USER="$BASENAME"
fi

# ─── Parse meta ──────────────────────────────────────────────────────────────
PKGACCT_VERSION="unknown"
PRIMARY_DOMAIN=""

if [[ -f "$SOURCE_ROOT/meta/pkgacctversion" ]]; then
    PKGACCT_VERSION="$(head -n1 "$SOURCE_ROOT/meta/pkgacctversion" 2>/dev/null | tr -d '\r\n' || echo unknown)"
fi
if [[ -f "$SOURCE_ROOT/version" ]]; then
    PKGACCT_VERSION="$(head -n1 "$SOURCE_ROOT/version" 2>/dev/null | tr -d '\r\n' || echo "$PKGACCT_VERSION")"
fi

# Primary domain from cp/<user> file (key=value)
CP_FILE="$SOURCE_ROOT/cp/$SOURCE_USER"
if [[ -f "$CP_FILE" ]]; then
    PRIMARY_DOMAIN="$(grep -E '^DNS(=|:)' "$CP_FILE" 2>/dev/null | head -n1 | sed -E 's/^DNS[=:][[:space:]]*//' | tr -d '\r' || true)"
    [[ -z "$PRIMARY_DOMAIN" ]] && \
        PRIMARY_DOMAIN="$(grep -E '^DOMAIN(=|:)' "$CP_FILE" 2>/dev/null | head -n1 | sed -E 's/^DOMAIN[=:][[:space:]]*//' | tr -d '\r' || true)"
fi

# ─── Helpers — JSON manifests ────────────────────────────────────────────────
EMAILS_JSON="$STAGING/emails.json"
FORWARDERS_JSON="$STAGING/forwarders.json"
DATABASES_JSON="$STAGING/databases.json"
DNS_ZONES_JSON="$STAGING/dns_zones.json"
SSL_CERTS_JSON="$STAGING/ssl_certs.json"
CRON_JOBS_JSON="$STAGING/cron_jobs.json"
FTP_JSON="$STAGING/ftp.json"
DOMAINS_JSON="$STAGING/domains.json"

EMAIL_COUNT=0
FORWARDER_COUNT=0
DB_COUNT=0
DNS_COUNT=0
SSL_COUNT=0
FTP_COUNT=0
CRON_COUNT=0
DOMAIN_COUNT=0
HOMEDIR_BYTES=0

# ─── 1) Homedir size + (execute) rsync ───────────────────────────────────────
if [[ -d "$SOURCE_ROOT/homedir" ]]; then
    HOMEDIR_BYTES="$(du -sb "$SOURCE_ROOT/homedir" 2>/dev/null | awk '{print $1}')"
    [[ -z "$HOMEDIR_BYTES" ]] && HOMEDIR_BYTES=0
fi

if [[ "$MODE" == "execute" ]] && [[ "$DRY_RUN" != "true" ]] && ! _is_skip homedir; then
    if [[ -d "$SOURCE_ROOT/homedir" ]]; then
        # home_base config'te /home/users → getent ile GERÇEK home'u çöz. Hardcoded
        # /home/<user> yanlış olurdu (provisionFull/onx-user-add /home/users/<user> açar).
        # ÖNEMLİ: getent, kullanıcı yoksa exit 2 verir; 2>/dev/null sadece stderr'i
        # susturur, exit 2 pipefail+set -e ile TÜM scripti öldürürdü (rollback → exit 4).
        # || true ile non-fatal yap; kullanıcı yoksa fallback /home/users/<user>.
        TARGET_HOME="$(getent passwd "$TARGET_USER" 2>/dev/null | cut -d: -f6 || true)"
        [[ -z "$TARGET_HOME" ]] && TARGET_HOME="/home/users/$TARGET_USER"
        mkdir -p "$TARGET_HOME"
        onx_log "cpanel-import: rsync homedir → $TARGET_HOME"

        if command -v rsync >/dev/null 2>&1; then
            rsync -a --no-owner --no-group "$SOURCE_ROOT/homedir/" "$TARGET_HOME/" \
                || onx_die 3 "rsync homedir başarısız"
        else
            cp -a "$SOURCE_ROOT/homedir/." "$TARGET_HOME/" \
                || onx_die 3 "cp homedir başarısız"
        fi

        # Ownership reset (target user must already exist via onx-user-add)
        if id -u "$TARGET_USER" >/dev/null 2>&1; then
            chown -R "$TARGET_USER:$TARGET_USER" "$TARGET_HOME" 2>/dev/null || true
        fi
        chmod 0750 "$TARGET_HOME/public_html" 2>/dev/null || true
    fi
fi

# ─── 2) Email accounts → emails.json ─────────────────────────────────────────
# cPanel stores email passwd as: local:domain:password_hash:quota:...
# password_hash is $6$salt$hash (SHA512-CRYPT) — Dovecot-compatible directly.
emit_emails() {
    # cPanel cpmove e-posta hesaplarını homedir/etc/<domain>/{passwd,shadow,quota}
    # altında tutar (diag ile doğrulandı). Eski varsayım etc/<user> YANLIŞTI →
    # hesaplar hiç bulunamıyordu. homedir/etc'i ÖNCE dene, eski yolları fallback bırak.
    local etc_dir="$SOURCE_ROOT/homedir/etc"
    [[ -d "$etc_dir" ]] || etc_dir="$SOURCE_ROOT/etc/$SOURCE_USER"
    [[ -d "$etc_dir" ]] || etc_dir="$SOURCE_ROOT/etc"
    [[ -d "$etc_dir" ]] || { echo "[]" > "$EMAILS_JSON"; return; }

    local first=1
    {
        printf '['
        # /etc/<user>/<domain>/{passwd,quota,shadow}
        for d_dir in "$etc_dir"/*/; do
            [[ -d "$d_dir" ]] || continue
            local domain
            domain="$(basename "$d_dir")"
            local pfile="${d_dir}passwd"
            local qfile="${d_dir}quota"
            local sfile="${d_dir}shadow"
            [[ -f "$pfile" ]] || continue

            # Quota lookup map
            declare -A QMAP
            QMAP=()
            if [[ -f "$qfile" ]]; then
                while IFS=: read -r q_local q_mb _ ; do
                    [[ -z "$q_local" ]] && continue
                    QMAP["$q_local"]="${q_mb:-1024}"
                done < "$qfile"
            fi

            # Shadow lookup (preferred hash source)
            declare -A SMAP
            SMAP=()
            if [[ -f "$sfile" ]]; then
                while IFS=: read -r s_local s_hash _ ; do
                    [[ -z "$s_local" ]] && continue
                    SMAP["$s_local"]="$s_hash"
                done < "$sfile"
            fi

            while IFS=: read -r local p_domain p_hash p_quota _; do
                [[ -z "$local" ]] && continue
                # Prefer shadow hash if present
                local hash="${SMAP[$local]:-$p_hash}"
                [[ -z "$hash" || "$hash" == "*" || "$hash" == "!" ]] && hash=""
                local quota="${QMAP[$local]:-${p_quota:-1024}}"
                [[ "$quota" =~ ^[0-9]+$ ]] || quota=1024

                # Dovecot SHA512-CRYPT scheme prefix if missing
                if [[ -n "$hash" && "${hash:0:1}" == '$' ]]; then
                    hash="{SHA512-CRYPT}${hash}"
                fi

                [[ $first -eq 0 ]] && printf ','
                first=0
                jq -nc \
                    --arg local "$local" \
                    --arg domain "$domain" \
                    --arg hash "$hash" \
                    --argjson quota_mb "$quota" \
                    '{local:$local, domain:$domain, password_hash:$hash, quota_mb:$quota_mb}'
                EMAIL_COUNT=$((EMAIL_COUNT + 1))
            done < "$pfile"
        done
        printf ']'
    } > "$EMAILS_JSON"
}

if ! _is_skip email; then
    emit_emails
else
    echo "[]" > "$EMAILS_JSON"
fi

# ─── 3) Forwarders → forwarders.json ─────────────────────────────────────────
# cPanel aliases:  source: dest1,dest2
emit_forwarders() {
    local first=1
    {
        printf '['
        for vali in "$SOURCE_ROOT/etc/valiases"/* "$SOURCE_ROOT/etc/$SOURCE_USER/valiases"/*; do
            [[ -f "$vali" ]] || continue
            local domain
            domain="$(basename "$vali")"
            while IFS= read -r line; do
                # Skip comments / blanks / *: defaults
                [[ -z "$line" || "${line:0:1}" == '#' ]] && continue
                [[ "$line" == \**: ]] && continue
                # Split "source: dest"
                local src dst
                src="${line%%:*}"
                dst="${line#*:}"
                src="$(echo "$src" | xargs)"
                dst="$(echo "$dst" | xargs)"
                [[ -z "$src" || -z "$dst" ]] && continue
                # source can be either "user" (→ user@domain) or full email
                if [[ "$src" != *@* ]]; then
                    src="${src}@${domain}"
                fi
                [[ $first -eq 0 ]] && printf ','
                first=0
                jq -nc \
                    --arg src "$src" \
                    --arg dst "$dst" \
                    --arg domain "$domain" \
                    '{source:$src, destinations:$dst, domain:$domain}'
                FORWARDER_COUNT=$((FORWARDER_COUNT + 1))
            done < "$vali"
        done
        printf ']'
    } > "$FORWARDERS_JSON"
}

if ! _is_skip email; then
    emit_forwarders
else
    echo "[]" > "$FORWARDERS_JSON"
fi

# ─── 3b) Mailbox İÇERİĞİ → /var/vmail (Maildir++) ────────────────────────────
# cPanel: homedir/mail/<domain>/<local>/{cur,new,tmp,.Folder...}. Hedef (Dovecot):
# /var/vmail/<domain>/<local>/Maildir/. Hesap DB satırını CpanelImporter (PHP) yazar;
# burada SADECE gerçek e-posta dosyalarını taşırız. Sahip vmail:vmail (5000:5000);
# parent dizini ÖNCE chown'la (LMTP "Permission denied" bug — onx-mailbox-create:76).
# dovecot index/uidlist'leri HARİÇ tut → Dovecot taze rebuild etsin (eski UID'ler stale).
migrate_mail_content() {
    local etc_dir="$SOURCE_ROOT/homedir/etc"
    local mail_root="$SOURCE_ROOT/homedir/mail"
    [[ -d "$etc_dir" && -d "$mail_root" ]] || return 0
    command -v rsync >/dev/null 2>&1 || { onx_log "cpanel-import: rsync yok — mail içeriği atlandı"; return 0; }

    local vmail_base="/var/vmail" moved=0
    local d_dir domain local _rest src dest
    for d_dir in "$etc_dir"/*/; do
        [[ -d "$d_dir" && -f "${d_dir}passwd" ]] || continue
        domain="$(basename "$d_dir")"
        [[ "$domain" =~ ^[a-zA-Z0-9.-]+$ ]] || continue
        while IFS=: read -r local _rest; do
            [[ -n "$local" ]] || continue
            [[ "$local" =~ ^[a-zA-Z0-9._-]+$ ]] || continue
            src="$mail_root/$domain/$local"
            [[ -d "$src" ]] || continue
            dest="$vmail_base/$domain/$local/Maildir"
            mkdir -p "$dest" || continue
            # parent dizinleri ÖNCE vmail'e ver (LMTP teslimat için şart)
            chown vmail:vmail "$vmail_base/$domain" 2>/dev/null || chown 5000:5000 "$vmail_base/$domain" 2>/dev/null || true
            chown vmail:vmail "$vmail_base/$domain/$local" 2>/dev/null || chown 5000:5000 "$vmail_base/$domain/$local" 2>/dev/null || true
            rsync -a --exclude='dovecot.index*' --exclude='dovecot-uidlist*' \
                  --exclude='dovecot.mailbox.log' --exclude='maildirsize' \
                  "$src/" "$dest/" 2>/dev/null || true
            chown -R vmail:vmail "$vmail_base/$domain/$local" 2>/dev/null \
                || chown -R 5000:5000 "$vmail_base/$domain/$local" 2>/dev/null || true
            # o-rwx ŞART: rsync -a cPanel'in 0644 (world-readable) mesaj izinlerini
            # korur → mail içeriği herkese-okunur sızardı (hakem MAJOR#1). Diğer
            # kullanıcılardan gizle; sahip+grup (vmail) okur/yazar.
            chmod -R u+rwX,g+rwX,o-rwx "$vmail_base/$domain/$local" 2>/dev/null || true
            moved=$((moved + 1))
            onx_log "cpanel-import: mailbox content → $dest"
        done < "${d_dir}passwd"
    done
    onx_log "cpanel-import: mail content migrated for ${moved} mailbox(es)"
}

if [[ "$MODE" == "execute" && "$DRY_RUN" != "true" ]] && ! _is_skip email; then
    migrate_mail_content || onx_log "cpanel-import: migrate_mail_content WARN (devam)"
fi

# ─── 4) MySQL databases → databases.json (+ execute restores) ────────────────
emit_databases() {
    local first=1
    {
        printf '['
        local mysql_dir="$SOURCE_ROOT/mysql"
        [[ -d "$mysql_dir" ]] || { printf ']'; return; }

        for sql_file in "$mysql_dir"/*.sql; do
            [[ -f "$sql_file" ]] || continue
            local original
            original="$(basename "$sql_file" .sql)"

            # Skip the inventory file itself (some pkgacct emit <user>.sql wrapper)
            [[ "$original" == "$SOURCE_USER" ]] && continue

            # Migration = lift-and-shift: DB adını PRESERVE et (yeniden adlandırma YOK).
            # Eskiden ourprefix_ ile rename ediliyordu → app config (wp-config DB_NAME,
            # .env DB_DATABASE) eski adı arar, bağlanamaz; ayrıca cPanel'in zaten
            # <cpaneluser>_<name> prefix'i var (collision düşük). Orijinal adı koruyunca
            # site sıfır düzenlemeyle çalışır ve mysql.sql GRANT'ları (4b) doğrudan geçerli.
            local new_name="$original"

            local size_bytes
            size_bytes="$(stat -c%s "$sql_file" 2>/dev/null || echo 0)"

            [[ $first -eq 0 ]] && printf ','
            first=0
            jq -nc \
                --arg orig "$original" \
                --arg new "$new_name" \
                --arg sql "$sql_file" \
                --argjson size "$size_bytes" \
                '{original_name:$orig, new_name:$new, sql_file:$sql, size_bytes:$size}'
            DB_COUNT=$((DB_COUNT + 1))
        done
        printf ']'
    } > "$DATABASES_JSON"
}

if ! _is_skip mysql; then
    emit_databases
else
    echo "[]" > "$DATABASES_JSON"
fi

if [[ "$MODE" == "execute" ]] && [[ "$DRY_RUN" != "true" ]] && ! _is_skip mysql; then
    # Iterate the just-emitted manifest and restore each DB.
    if command -v mysql >/dev/null 2>&1; then
        while IFS= read -r row; do
            new_name="$(echo "$row" | jq -r '.new_name')"
            sql_file="$(echo "$row" | jq -r '.sql_file')"
            [[ -z "$new_name" || -z "$sql_file" ]] && continue
            # Defensive — only allow ^[a-zA-Z0-9_]+$ names
            [[ "$new_name" =~ ^[a-zA-Z0-9_]+$ ]] || {
                onx_log "cpanel-import: SKIP unsafe db name '$new_name'"
                continue
            }
            # Bare `mysql` root@localhost (using password: NO) ile bağlanır → 1045.
            # mysql_exec_root /root/.onox-mysql-root.cnf'yi (panel'in root cnf'i) okur;
            # CREATE DATABASE + GRANT app-user'ın yetkisini aşar, root şart.
            mysql_exec_root "" "CREATE DATABASE IF NOT EXISTS \`${new_name}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" \
                || onx_die 3 "mysql create DB failed: $new_name"
            # Stream import (büyük dump'larda RAM patlamasın) — root stdin.
            mysql_exec_root_stdin "$new_name" < "$sql_file" \
                || onx_die 3 "mysql import failed: $new_name"
            onx_log "cpanel-import: imported db=$new_name"
        done < <(jq -c '.[]' "$DATABASES_JSON")
    else
        onx_log "cpanel-import: mysql client not found — skipping DB restore"
    fi
fi

# ─── 4b) MySQL kullanıcıları + yetkileri (mysql.sql grants) ──────────────────
# cPanel grants dosyası ($SOURCE_ROOT/mysql.sql):
#   GRANT USAGE ON *.* TO 'u'@'host' IDENTIFIED BY PASSWORD '*HASH';   (kullanıcı+şifre)
#   GRANT ALL PRIVILEGES ON `db`.* TO 'u'@'host';                      (yetki)
# DB adı PRESERVE edildiği için orijinal db'ye grant verilir → app config'leri
# (DB_USER/DB_PASSWORD) sıfır düzenlemeyle bağlanır (hash birebir korunur).
# GÜVENLİK: yalnız @localhost/@127.0.0.1; user/host/hash/db KATI doğrulanır ve
# statement token'lardan YENİDEN kurulur (dosyadan ham SQL asla mysql'e geçmez).
restore_db_users() {
    local grants="$SOURCE_ROOT/mysql.sql"
    [[ -f "$grants" ]] || { onx_log "cpanel-import: mysql.sql yok — db-user atlandı"; return 0; }
    command -v mysql >/dev/null 2>&1 || return 0

    local sqlfile; sqlfile="$(mktemp)" || return 0
    # cPanel db adını `db\_x` diye escape'ler. awk'ta ters-bölü regex'i taşınabilir
    # değil → önce tr ile TÜM ters-bölüleri sil (geçerli identifier/hash/user'da
    # ters-bölü olmaz, güvenli). awk içinde tek-tırnak için q="\047".
    # set -e altında awk/tr farkı (mawk vs gawk) migration'ı öldürmesin → guard.
    tr -d '\\' < "$grants" 2>/dev/null | awk '
        BEGIN { q = "\047" }
        function emit_user(line,   rest,u,host,hash) {
            if (line !~ /IDENTIFIED BY PASSWORD/) return
            rest = line; sub(/.*TO[ \t]+/, "", rest)
            u = rest;    sub("^" q, "", u);    sub(q "@.*", "", u)
            host = rest; sub("^" q "[^" q "]*" q "@" q, "", host); sub(q "[ \t]+IDENTIFIED.*", "", host)
            hash = line; sub(".*IDENTIFIED BY PASSWORD[ \t]+" q, "", hash); sub(q "[ \t]*;.*", "", hash)
            if (u ~ /^[A-Za-z0-9_]+$/ && (host == "localhost" || host == "127.0.0.1") && hash ~ /^\*[A-Fa-f0-9]{40}$/)
                printf "CREATE USER IF NOT EXISTS %s%s%s@%s%s%s IDENTIFIED BY PASSWORD %s%s%s;\n", q,u,q, q,host,q, q,hash,q
        }
        function emit_grant(line,   rest,db,u,host) {
            db = line;   sub(/.*ON[ \t]+`/, "", db); sub(/`.*/, "", db)
            rest = line; sub(/.*TO[ \t]+/, "", rest)
            u = rest;    sub("^" q, "", u);    sub(q "@.*", "", u)
            host = rest; sub("^" q "[^" q "]*" q "@" q, "", host); sub(q "[ \t]*;.*", "", host)
            if (db ~ /^[A-Za-z0-9_]+$/ && u ~ /^[A-Za-z0-9_]+$/ && (host == "localhost" || host == "127.0.0.1"))
                printf "GRANT ALL PRIVILEGES ON `%s`.* TO %s%s%s@%s%s%s;\n", db, q,u,q, q,host,q
        }
        $0 ~ ("@" q "(localhost|127\\.0\\.0\\.1)" q) {
            if ($0 ~ /GRANT[ \t]+USAGE/)        emit_user($0)
            else if ($0 ~ /PRIVILEGES[ \t]+ON/) emit_grant($0)
        }
        END { print "FLUSH PRIVILEGES;" }
    ' > "$sqlfile" || true

    local stmts; stmts="$(grep -c ';' "$sqlfile" 2>/dev/null || echo 0)"
    if [[ "${stmts:-0}" -gt 1 ]]; then
        if mysql_exec_root_stdin "" < "$sqlfile"; then
            onx_log "cpanel-import: db-users restored (${stmts} stmt)"
        else
            onx_log "cpanel-import: db-user restore WARN (devam ediliyor)"
        fi
    else
        onx_log "cpanel-import: mysql.sql'de @localhost grant bulunamadı"
    fi
    rm -f "$sqlfile"
}

if [[ "$MODE" == "execute" ]] && [[ "$DRY_RUN" != "true" ]] && ! _is_skip mysql; then
    # Best-effort: grant/user hatası tüm migration'ı ÖLDÜRMESİN (warn + devam).
    restore_db_users || onx_log "cpanel-import: restore_db_users WARN (devam ediliyor)"
fi

# ─── 5) DNS zones → dns_zones.json (records parsed) ──────────────────────────
emit_dns_zones() {
    local first=1
    {
        printf '['
        local dz_dir="$SOURCE_ROOT/dnszones"
        [[ -d "$dz_dir" ]] || { printf ']'; return; }
        for zfile in "$dz_dir"/*.db "$dz_dir"/*.zone "$dz_dir"/*; do
            [[ -f "$zfile" ]] || continue
            # Only accept plausible zone names
            local fname
            fname="$(basename "$zfile")"
            local domain="${fname%.db}"
            domain="${domain%.zone}"

            # Records → JSON array via awk (mirrors onx-bind-zone-parse)
            local records
            records="$(awk -v dom="$domain" '
                BEGIN { ttl_default = 14400; first = 1; printf "[" }
                /^;/ || /^$/ { next }
                /^\$TTL/ { gsub(/[^0-9]/, "", $2); ttl_default = $2; next }
                /^\$/ { next }
                { gsub(/[ \t]+;.*$/, "") }
                /^[A-Za-z0-9@_\.*-]/ {
                    name = $1
                    idx = 2
                    ttl = ttl_default
                    if ($idx ~ /^[0-9]+$/) { ttl = $idx; idx++ }
                    if (toupper($idx) == "IN") idx++
                    type = toupper($idx); idx++
                    if (type == "SOA" || type == "RRSIG" || type == "NSEC" || type == "DNSKEY" || type == "DS") next
                    rdata = ""
                    for (i = idx; i <= NF; i++) { if (i > idx) rdata = rdata " "; rdata = rdata $i }
                    prio = 0
                    if (type == "MX" || type == "SRV") {
                        split(rdata, parts, " ")
                        if (parts[1] ~ /^[0-9]+$/) { prio = parts[1]; rdata = parts[2]; for (i=3; i<=length(parts); i++) rdata = rdata " " parts[i] }
                    }
                    gsub(/\\/, "\\\\", rdata); gsub(/"/, "\\\"", rdata)
                    gsub(/\\/, "\\\\", name); gsub(/"/, "\\\"", name)
                    if (name == "@") name = dom
                    if (!first) printf ","
                    first = 0
                    printf "{\"name\":\"%s\",\"type\":\"%s\",\"ttl\":%d,\"prio\":%d,\"content\":\"%s\"}", name, type, ttl, prio, rdata
                }
                END { print "]" }
            ' "$zfile")"

            local rcount
            rcount="$(echo "$records" | jq 'length' 2>/dev/null || echo 0)"

            [[ $first -eq 0 ]] && printf ','
            first=0
            jq -nc \
                --arg domain "$domain" \
                --argjson records "$records" \
                --argjson rcount "$rcount" \
                '{domain:$domain, record_count:$rcount, records:$records}'
            DNS_COUNT=$((DNS_COUNT + 1))
        done
        printf ']'
    } > "$DNS_ZONES_JSON"
}

if ! _is_skip dns; then
    emit_dns_zones
else
    echo "[]" > "$DNS_ZONES_JSON"
fi

# ─── 6) SSL certs → ssl_certs.json (+ execute copies) ────────────────────────
emit_ssl_certs() {
    local first=1
    local ssl_stage="$STAGING/ssl"
    mkdir -p "$ssl_stage" 2>/dev/null || true
    {
        printf '['
        # ── (A) Yeni cPanel: apache_tls/<domain> = TEK birleşik dosya ───────────
        # İçinde peş peşe PEM blokları (leaf cert + private key + CA chain). Bloklara
        # ayırıp INLINE içerik olarak emit ediyoruz → PHP (non-root) chowned JSON'dan
        # okur (cert dosyalarına ayrı izin gerekmez). installManual cert+key+chain ister.
        local atls_root="$SOURCE_ROOT/apache_tls"
        if [[ -d "$atls_root" ]]; then
            local atls dom cfile kfile chfile expires
            for atls in "$atls_root"/*; do
                [[ -f "$atls" ]] || continue
                dom="$(basename "$atls")"
                [[ "$dom" =~ ^[a-zA-Z0-9.-]+$ ]] || continue
                grep -q 'BEGIN CERTIFICATE' "$atls" 2>/dev/null || continue
                cfile="$ssl_stage/${dom}.crt"; kfile="$ssl_stage/${dom}.key"; chfile="$ssl_stage/${dom}.chain"
                : > "$cfile"; : > "$kfile"; : > "$chfile"
                awk -v cf="$cfile" -v kf="$kfile" -v chf="$chfile" '
                    /-----BEGIN/ {
                        if ($0 ~ /PRIVATE KEY/) { mode="k" }
                        else if ($0 ~ /CERTIFICATE/) { cn++; mode=(cn==1 ? "c" : "h") }
                        else { mode="" }
                    }
                    mode=="k" { print > kf }
                    mode=="c" { print > cf }
                    mode=="h" { print > chf }
                    /-----END/ { mode="" }
                ' "$atls" 2>/dev/null || true
                [[ -s "$cfile" && -s "$kfile" ]] || continue
                expires=""
                command -v openssl >/dev/null 2>&1 && \
                    expires="$(openssl x509 -in "$cfile" -noout -enddate 2>/dev/null | sed 's/notAfter=//' || true)"
                [[ $first -eq 0 ]] && printf ','
                first=0
                jq -nc \
                    --arg domain "$dom" \
                    --arg cert "$(cat "$cfile" 2>/dev/null)" \
                    --arg key "$(cat "$kfile" 2>/dev/null)" \
                    --arg chain "$(cat "$chfile" 2>/dev/null)" \
                    --arg expires "$expires" \
                    '{domain:$domain, certificate:$cert, private_key:$key, chain:$chain, expires_at:$expires}'
                SSL_COUNT=$((SSL_COUNT + 1))
            done
        fi
        # ── (B) Eski cPanel: ssl/certs + ssl/keys (ayrı dosyalar) ───────────────
        local ssl_root="$SOURCE_ROOT/ssl"
        if [[ -d "$ssl_root" ]]; then
            local cert_dir crt cn key kd expires
            for cert_dir in "$ssl_root/certs" "$ssl_root/cert"; do
                [[ -d "$cert_dir" ]] || continue
                for crt in "$cert_dir"/*.crt "$cert_dir"/*; do
                    [[ -f "$crt" ]] || continue
                    head -n1 "$crt" 2>/dev/null | grep -q 'BEGIN CERTIFICATE' || continue
                    cn="$(basename "$crt")"; cn="${cn%.crt}"
                    [[ "$cn" =~ ^[a-zA-Z0-9.-]+$ ]] || continue
                    key=""
                    for kd in "$ssl_root/keys" "$ssl_root/key"; do
                        [[ -f "$kd/${cn}.key" ]] && { key="$kd/${cn}.key"; break; }
                    done
                    [[ -n "$key" && -f "$key" ]] || continue
                    expires=""
                    command -v openssl >/dev/null 2>&1 && \
                        expires="$(openssl x509 -in "$crt" -noout -enddate 2>/dev/null | sed 's/notAfter=//' || true)"
                    [[ $first -eq 0 ]] && printf ','
                    first=0
                    jq -nc \
                        --arg domain "$cn" \
                        --arg cert "$(cat "$crt" 2>/dev/null)" \
                        --arg key "$(cat "$key" 2>/dev/null)" \
                        --arg chain "" \
                        --arg expires "$expires" \
                        '{domain:$domain, certificate:$cert, private_key:$key, chain:$chain, expires_at:$expires}'
                    SSL_COUNT=$((SSL_COUNT + 1))
                done
            done
        fi
        printf ']'
    } > "$SSL_CERTS_JSON"
}

if ! _is_skip ssl; then
    emit_ssl_certs
else
    echo "[]" > "$SSL_CERTS_JSON"
fi

# SSL kurulumu artık PHP tarafında: CpanelImporter::ingestSsl() ssl_certs.json'daki
# inline PEM'i SslProvisioner::installManual'e verir + vhost'a bağlar (apache_tls
# birleşik dosyası emit_ssl_certs'te zaten cert/key/chain'e ayrıldı). Eski dead-drop
# kopya (/etc/onoxsoft/imported-ssl) kaldırıldı — kullanılmıyordu.

# ─── 7) Cron jobs → cron_jobs.json (+ execute write) ─────────────────────────
emit_cron() {
    local first=1
    {
        printf '['
        local cron_file="$SOURCE_ROOT/cron/$SOURCE_USER"
        [[ -f "$cron_file" ]] || { printf ']'; return; }
        while IFS= read -r line; do
            line="${line%%$'\r'}"
            # Strip blanks and comments and MAILTO/PATH/SHELL env lines
            [[ -z "$line" ]] && continue
            [[ "${line:0:1}" == '#' ]] && continue
            [[ "$line" == MAILTO=* || "$line" == PATH=* || "$line" == SHELL=* ]] && continue

            # Parse cron expression — first 5 fields = schedule, rest = command
            local m h d mo w
            local rest
            read -r m h d mo w rest <<<"$line"
            [[ -z "$rest" ]] && continue

            [[ $first -eq 0 ]] && printf ','
            first=0
            jq -nc \
                --arg m "$m" --arg h "$h" --arg d "$d" \
                --arg mo "$mo" --arg w "$w" --arg cmd "$rest" \
                '{minute:$m, hour:$h, day:$d, month:$mo, weekday:$w, command:$cmd, expression:($m+" "+$h+" "+$d+" "+$mo+" "+$w)}'
            CRON_COUNT=$((CRON_COUNT + 1))
        done < "$cron_file"
        printf ']'
    } > "$CRON_JOBS_JSON"
}

if ! _is_skip cron; then
    emit_cron
else
    echo "[]" > "$CRON_JOBS_JSON"
fi

if [[ "$MODE" == "execute" ]] && [[ "$DRY_RUN" != "true" ]] && ! _is_skip cron; then
    CRON_DST="/etc/cron.d/onoxsoft-$TARGET_USER"
    if [[ -s "$CRON_JOBS_JSON" ]] && [[ "$(jq 'length' "$CRON_JOBS_JSON" 2>/dev/null || echo 0)" -gt 0 ]]; then
        {
            printf '# Onoxsoft cPanel import (%s → %s)\n' "$SOURCE_USER" "$TARGET_USER"
            printf 'SHELL=/bin/bash\nPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\nMAILTO=""\n\n'
            jq -r --arg user "$TARGET_USER" \
                '.[] | "\(.minute) \(.hour) \(.day) \(.month) \(.weekday) \($user) \(.command)"' \
                "$CRON_JOBS_JSON"
        } > "$CRON_DST.tmp"
        chmod 0644 "$CRON_DST.tmp"
        chown root:root "$CRON_DST.tmp" 2>/dev/null || true
        mv -f "$CRON_DST.tmp" "$CRON_DST"
        onx_log "cpanel-import: cron written → $CRON_DST"
    fi
fi

# ─── 8) FTP hesapları → ftp.json ─────────────────────────────────────────────
# cPanel proftpdpasswd (arşiv kökü): user:hash:uid:gid:gecos:homedir:shell.
# ATLA: ana hesap (= SOURCE_USER = bizdeki sistem kullanıcısı, provision'da var) +
# cPanel _logs/sistem hesapları (homedir kullanıcı home'u DIŞINDA, ör. /etc/apache2/logs).
# Sadece GERÇEK ek FTP hesapları emit edilir. Şifre korunamaz (cPanel crypt hash) →
# PHP tarafı (ingestFtp) yeni rastgele şifre üretir + uyarır.
emit_ftp() {
    local pfile="$SOURCE_ROOT/proftpdpasswd"
    [[ -f "$pfile" ]] || pfile="$SOURCE_ROOT/homedir/etc/proftpd/passwd"
    [[ -f "$pfile" ]] || { echo "[]" > "$FTP_JSON"; return; }

    local home_prefix="/home/$SOURCE_USER"
    local first=1
    local user _hash _uid _gid _gecos homedir _shell logical reldir
    {
        printf '['
        while IFS=: read -r user _hash _uid _gid _gecos homedir _shell; do
            [[ -z "$user" ]] && continue
            [[ "$user" == "$SOURCE_USER" ]] && continue       # ana hesap = sistem kullanıcısı
            # home DIŞI (logs/sistem) → atla. '/' ŞART: aksi halde /home/<user>-backup
            # gibi PREFIX-kardeş dizinler de eşleşip kaçardı (hakem M1).
            [[ "$homedir" == "$home_prefix"/* || "$homedir" == "$home_prefix" ]] || continue
            logical="${user%@*}"                              # @domain varsa local part
            logical="$(printf '%s' "$logical" | tr 'A-Z' 'a-z' | tr -cd 'a-z0-9_' | cut -c1-31)"
            # Geçerli logical garanti et (FtpProvisioner regex ^[a-z][a-z0-9_]{2,31}$):
            # harf ile başlamıyorsa 'ftp' öne ekle, kısa kalırsa doldur → sessiz DROP yok (hakem m1).
            [[ "$logical" =~ ^[a-z] ]] || logical="ftp${logical}"
            [[ ${#logical} -ge 3 ]] || logical="${logical}ftp"
            logical="$(printf '%s' "$logical" | cut -c1-31)"
            [[ -n "$logical" ]] || continue
            reldir="${homedir#"$home_prefix"}"; reldir="${reldir#/}"
            [[ $first -eq 0 ]] && printf ','
            first=0
            jq -nc --arg user "$user" --arg logical "$logical" \
                --arg home_dir "$homedir" --arg relative_dir "$reldir" \
                '{ftp_user:$user, logical:$logical, home_dir:$home_dir, relative_dir:$relative_dir}'
            FTP_COUNT=$((FTP_COUNT + 1))
        done < "$pfile"
        printf ']'
    } > "$FTP_JSON"
}

if ! _is_skip ftp; then
    emit_ftp
else
    echo "[]" > "$FTP_JSON"
fi

# ─── 9) Addon / parked / subdomain → domains.json ────────────────────────────
# Domain config bu cPanel varyantında (jupiter) KÖK seviyede `userdata/` altında
# (meta/userdata DEĞİL). `userdata/main` üç listeyi tutar:
#   addon_domains: {}            # boş; dolu: "  addon.com: addon_primary.com" (map)
#   parked_domains: []           # boş; dolu: "  - parked.com" (list)
#   sub_domains: []              # boş; dolu: "  - sub.primary.com" (list)
# Her domainin per-domain dosyası `userdata/<domain>` → documentroot + homedir.
# addon'un dahili sub'ı (map değeri) sub_domains'te de görünür → EXCLUDE ediyoruz.
# docroot eski home altında → TARGET_HOME'a yeniden yazılıp emit edilir (homedir
# rsync'i zaten dosyaları TARGET_HOME'a taşıdı → path birebir oturur).
emit_domains() {
    local main_file="$SOURCE_ROOT/userdata/main"
    [[ -f "$main_file" ]] || { echo "[]" > "$DOMAINS_JSON"; return; }

    # TARGET_HOME execute'ta set edilir; dry-run/güvenlik için fallback çöz.
    local thome="$TARGET_HOME"
    if [[ -z "$thome" ]]; then
        thome="$(getent passwd "$TARGET_USER" 2>/dev/null | cut -d: -f6 || true)"
        [[ -z "$thome" ]] && thome="/home/users/$TARGET_USER"
    fi

    # Primary'i lowercase karşılaştır (cp DNS= mixed-case olabilir; domain zaten lc).
    local pdom_lc
    pdom_lc="$(printf '%s' "$PRIMARY_DOMAIN" | tr 'A-Z' 'a-z' | tr -d '\r')"

    local first=1
    local type domain udfile docroot src_home rel parent docroot_new hd
    {
        printf '['
        while IFS=$'\t' read -r type domain; do
            [[ -z "$type" || -z "$domain" ]] && continue
            domain="$(printf '%s' "$domain" | tr 'A-Z' 'a-z' | tr -d '\r')"
            [[ "$domain" =~ ^[a-z0-9.-]+\.[a-z]{2,}$ ]] || continue
            [[ "$domain" == "$pdom_lc" ]] && continue   # birincil = zaten provisioned

            # per-domain userdata → documentroot + homedir
            udfile="$SOURCE_ROOT/userdata/$domain"
            docroot=""; src_home="/home/$SOURCE_USER"
            if [[ -f "$udfile" ]]; then
                docroot="$(awk -F': ' '/^documentroot:/{print $2; exit}' "$udfile" | tr -d ' \r')"
                hd="$(awk -F': ' '/^homedir:/{print $2; exit}' "$udfile" | tr -d ' \r')"
                [[ -n "$hd" ]] && src_home="$hd"
            fi

            # docroot'u yeni home'a yeniden yaz: SADECE docroot gerçekten src_home
            # altındaysa prefix'i çıkar (aksi halde "TARGET_HOME//home/olduser/..."
            # çift-path oluşurdu — hakem MINOR). Değilse tip-bazlı fallback'e düş.
            rel=""
            [[ -n "$docroot" && "$docroot" == "$src_home"/* ]] && rel="${docroot#"$src_home"/}"
            if [[ -z "$rel" ]]; then
                case "$type" in
                    addon)     rel="$domain" ;;
                    subdomain) rel="public_html/${domain%%.*}" ;;
                    *)         rel="public_html" ;;   # parked = main docroot
                esac
            fi
            docroot_new="${thome%/}/$rel"

            # parent: parked → primary; subdomain → ilk label atılmış hali
            parent=""
            case "$type" in
                parked)    parent="$PRIMARY_DOMAIN" ;;
                subdomain) parent="${domain#*.}" ;;
            esac

            [[ $first -eq 0 ]] && printf ','
            first=0
            jq -nc --arg name "$domain" --arg type "$type" \
                --arg doc_root "$docroot_new" --arg parent "$parent" \
                '{name:$name, type:$type, doc_root:$doc_root, parent:$parent}'
            DOMAIN_COUNT=$((DOMAIN_COUNT + 1))
        done < <(awk '
            # PASS 1 (dosya 1. kez): addon dahili-sub değerlerini topla — KEY SIRASINDAN
            # BAĞIMSIZ (sub_domains, addon_domains''ten önce gelse bile dışlama çalışsın; hakem MAJOR).
            FNR==NR {
                if ($0 ~ /^addon_domains:/) { p1=($0 ~ /\{\}/)?0:1; next }
                if ($0 ~ /^[A-Za-z_]/)      { p1=0 }
                if (p1 && $0 ~ /^[[:space:]]+[^[:space:]#-]/) { if($2!="")addonsub[$2]=1 }
                next
            }
            # PASS 2 (dosya 2. kez): sınıflandır + emit
            /^addon_domains:/  { if ($0 ~ /\{\}/) sec=""; else sec="addon";  next }
            /^parked_domains:/ { if ($0 ~ /\[\]/) sec=""; else sec="parked"; next }
            /^sub_domains:/    { if ($0 ~ /\[\]/) sec=""; else sec="sub";    next }
            /^[A-Za-z_]/       { sec="" }
            sec=="addon"  && /^[[:space:]]+[^[:space:]#-]/ { k=$1; sub(/:$/,"",k); if(k!="")print "addon\t" k; next }
            sec=="parked" && /^[[:space:]]*-[[:space:]]+/  { if($2!="")print "parked\t" $2; next }
            sec=="sub"    && /^[[:space:]]*-[[:space:]]+/  { if($2!="" && !($2 in addonsub))print "subdomain\t" $2; next }
        ' "$main_file" "$main_file")
        printf ']'
    } > "$DOMAINS_JSON"
}

if ! _is_skip domains; then
    emit_domains
else
    echo "[]" > "$DOMAINS_JSON"
fi

# ─── Audit + output ──────────────────────────────────────────────────────────
onx_audit "onx-cpanel-import" "mode=$MODE source=$SOURCE_USER target=$TARGET_USER stagedir=$STAGING"

# On execute success we keep the staging dir for Laravel ingest; the caller is
# responsible for cleanup after committing to the DB.
trap - ERR

# ── Manifest'leri panel (non-root queue worker) OKUYABİLSİN ──────────────────
# Staging root:root → CpanelImporter::readManifest (file_get_contents) Permission
# denied alır, ingest 0 satır yazar (DB/mail/dns panelde BOŞ görünür — kartazsa
# vakası). Arşiv dizininin (storage/app/migrations = panel-owned) sahibini staging
# dizinine + manifest DOSYALARINA devret. extracted/ tree'yi chown ETME (büyük).
# 0640: yalnız panel kullanıcısı okur (mail hash'leri world-readable OLMASIN).
if [[ "$MODE" == "execute" ]]; then
    REF_DIR="$(dirname "$ARCHIVE_REAL")"
    chown --reference="$REF_DIR" "$STAGING" 2>/dev/null || true
    for _mf in "$EMAILS_JSON" "$FORWARDERS_JSON" "$DATABASES_JSON" \
               "$DNS_ZONES_JSON" "$SSL_CERTS_JSON" "$CRON_JOBS_JSON" \
               "$FTP_JSON" "$DOMAINS_JSON"; do
        if [[ -f "$_mf" ]]; then
            chown --reference="$REF_DIR" "$_mf" 2>/dev/null || true
            chmod 0640 "$_mf" 2>/dev/null || true
        fi
    done
fi

# Build the final response
jq -n \
    --arg src "$SOURCE_USER" \
    --arg dst "$TARGET_USER" \
    --arg pkg "$PKGACCT_VERSION" \
    --arg pdom "$PRIMARY_DOMAIN" \
    --arg stagedir "$STAGING" \
    --arg mode "$MODE" \
    --argjson dry "$DRY_RUN" \
    --argjson skip "$SKIP_JSON" \
    --argjson home_bytes "$HOMEDIR_BYTES" \
    --argjson emails "$EMAIL_COUNT" \
    --argjson fwds "$FORWARDER_COUNT" \
    --argjson dbs "$DB_COUNT" \
    --argjson dns "$DNS_COUNT" \
    --argjson ssls "$SSL_COUNT" \
    --argjson crons "$CRON_COUNT" \
    --argjson ftps "$FTP_COUNT" \
    --argjson doms "$DOMAIN_COUNT" \
    --arg emails_f "$EMAILS_JSON" \
    --arg fwds_f "$FORWARDERS_JSON" \
    --arg dbs_f "$DATABASES_JSON" \
    --arg dns_f "$DNS_ZONES_JSON" \
    --arg ssl_f "$SSL_CERTS_JSON" \
    --arg cron_f "$CRON_JOBS_JSON" \
    --arg ftp_f "$FTP_JSON" \
    --arg doms_f "$DOMAINS_JSON" \
    '{
        source_user: $src,
        target_user: $dst,
        pkgacct_version: $pkg,
        primary_domain: $pdom,
        staging_dir: $stagedir,
        mode: $mode,
        dry_run: $dry,
        skip: $skip,
        imported: {
            homedir_bytes: $home_bytes,
            emails: $emails,
            forwarders: $fwds,
            databases: $dbs,
            dns_zones: $dns,
            ssl_certs: $ssls,
            cron_jobs: $crons,
            ftp: $ftps,
            domains: $doms
        },
        manifests: {
            emails:     $emails_f,
            forwarders: $fwds_f,
            databases:  $dbs_f,
            dns_zones:  $dns_f,
            ssl_certs:  $ssl_f,
            cron_jobs:  $cron_f,
            ftp:        $ftp_f,
            domains:    $doms_f
        }
    }'
