#!/usr/bin/env bash
set -euo pipefail

MARIADB_USAGE="${MARIADB_USAGE:-25}"
MIN_BUFFER_POOL_MB=256
MAX_CONNECTIONS_MIN=80
MAX_CONNECTIONS_MAX=300

usage() {
    cat <<'EOF'
Usage:
  my-cnf-gen.sh [--output PATH]

Generates a conservative MariaDB 10.6 my.cnf for DirectAdmin servers.
The InnoDB buffer pool is sized from MARIADB_USAGE percent of RAM so other DA services
such as web, exim, dovecot, redis and spam filtering keep enough memory.
EOF
}

read_mem_total_mb() {
    local mem_bytes

    if [[ -n "${MYSQL_GEN_MEM_MB:-}" ]]; then
        printf '%s\n' "$MYSQL_GEN_MEM_MB"
        return 0
    fi

    if [[ -r /proc/meminfo ]]; then
        awk '/^MemTotal:/ { printf "%d\n", int($2 / 1024) }' /proc/meminfo
        return 0
    fi

    mem_bytes="$(sysctl -n hw.memsize 2>/dev/null || true)"
    if [[ -n "$mem_bytes" ]]; then
        printf '%d\n' "$((mem_bytes / 1024 / 1024))"
        return 0
    fi

    echo "Unable to detect total memory" >&2
    exit 1
}

read_cpu_cores() {
    local cores
    cores=""

    if [[ -n "${MYSQL_GEN_CPU_CORES:-}" ]]; then
        printf '%s\n' "$MYSQL_GEN_CPU_CORES"
        return 0
    fi

    if [[ -r /proc/cpuinfo ]]; then
        cores="$(grep -c '^processor[[:space:]]*:' /proc/cpuinfo 2>/dev/null || true)"
    fi
    if [[ -z "$cores" || "$cores" -lt 1 ]]; then
        cores="$(getconf _NPROCESSORS_ONLN 2>/dev/null || true)"
    fi
    if [[ -z "$cores" || "$cores" -lt 1 ]]; then
        cores=1
    fi
    printf '%s\n' "$cores"
}

round_down_mb() {
    local value_mb="$1"
    local step_mb="$2"
    local rounded

    rounded=$((value_mb / step_mb * step_mb))
    if (( rounded < step_mb )); then
        rounded="$step_mb"
    fi
    printf '%s\n' "$rounded"
}

format_size() {
    local value_mb="$1"

    if (( value_mb >= 1024 && value_mb % 1024 == 0 )); then
        printf '%sG\n' "$((value_mb / 1024))"
    else
        printf '%sM\n' "$value_mb"
    fi
}

calc_buffer_pool_mb() {
    local mem_mb="$1"
    local pool_mb

    pool_mb=$((mem_mb * MARIADB_USAGE / 100))
    if (( pool_mb < MIN_BUFFER_POOL_MB )); then
        pool_mb="$MIN_BUFFER_POOL_MB"
    fi
    round_down_mb "$pool_mb" 128
}

calc_buffer_pool_instances() {
    local pool_mb="$1"
    local cores="$2"
    local instances=1

    if (( pool_mb >= 8192 && cores >= 4 )); then
        instances=4
    elif (( pool_mb >= 4096 && cores >= 2 )); then
        instances=2
    fi
    printf '%s\n' "$instances"
}

calc_max_connections() {
    local mem_mb="$1"
    local cores="$2"
    local connections

    connections=$((mem_mb / 128))
    if (( connections < MAX_CONNECTIONS_MIN )); then
        connections="$MAX_CONNECTIONS_MIN"
    fi
    if (( connections > MAX_CONNECTIONS_MAX )); then
        connections="$MAX_CONNECTIONS_MAX"
    fi
    if (( cores <= 2 && connections > 120 )); then
        connections=120
    fi
    printf '%s\n' "$connections"
}

calc_log_file_mb() {
    local pool_mb="$1"
    local log_mb

    log_mb=$((pool_mb / 4))
    if (( log_mb < 128 )); then
        log_mb=128
    elif (( log_mb > 1024 )); then
        log_mb=1024
    fi
    round_down_mb "$log_mb" 128
}

generate_config() {
    local mem_mb="$1"
    local cores="$2"
    local pool_mb="$3"
    local pool_instances="$4"
    local max_connections="$5"
    local log_file_mb="$6"
    local io_capacity="$7"
    local io_capacity_max="$8"

    cat <<EOF
# Generated by my-cnf-gen.sh for MariaDB 10.6 and DirectAdmin stack hosts.
# Detected RAM: ${mem_mb}M
# Detected CPU cores: ${cores}
# MariaDB memory budget: ${MARIADB_USAGE}% RAM

[client]
port=3306
socket=/var/lib/mysql/mysql.sock
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4

[mysqld]
user=mysql
port=3306
socket=/var/lib/mysql/mysql.sock
pid-file=/var/lib/mysql/mysql.pid
datadir=/var/lib/mysql

local_infile=0
skip_name_resolve=1
symbolic_links=0
max_connect_errors=1000000
max_allowed_packet=64M

character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
skip-character-set-client-handshake

default_storage_engine=InnoDB
myisam_recover_options=FORCE,BACKUP
key_buffer_size=32M

max_connections=${max_connections}
max_user_connections=50
thread_cache_size=64
table_open_cache=2048
table_definition_cache=1024
open_files_limit=65535

sort_buffer_size=512K
join_buffer_size=512K
read_buffer_size=256K
read_rnd_buffer_size=512K

tmp_table_size=32M
max_heap_table_size=32M

query_cache_type=0
query_cache_size=0

innodb_buffer_pool_size=$(format_size "$pool_mb")
innodb_buffer_pool_instances=${pool_instances}
innodb_log_file_size=$(format_size "$log_file_mb")
innodb_log_files_in_group=2
innodb_flush_log_at_trx_commit=1
innodb_flush_method=O_DIRECT
innodb_file_per_table=1
innodb_io_capacity=${io_capacity}
innodb_io_capacity_max=${io_capacity_max}

log_error=/var/lib/mysql/mysql-error.log
slow_query_log=0
slow_query_log_file=/var/lib/mysql/mysql-slow.log
long_query_time=2
log_queries_not_using_indexes=0
EOF
}

main() {
    local output=""
    local mem_mb
    local cores
    local pool_mb
    local pool_instances
    local max_connections
    local log_file_mb
    local io_capacity
    local io_capacity_max

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --output)
                shift
                [[ $# -gt 0 ]] || { echo "Missing value for --output" >&2; exit 1; }
                output="$1"
                ;;
            --help|-h)
                usage
                exit 0
                ;;
            *)
                echo "Unknown argument: $1" >&2
                usage >&2
                exit 1
                ;;
        esac
        shift
    done

    mem_mb="$(read_mem_total_mb)"
    cores="$(read_cpu_cores)"
    pool_mb="$(calc_buffer_pool_mb "$mem_mb")"
    pool_instances="$(calc_buffer_pool_instances "$pool_mb" "$cores")"
    max_connections="$(calc_max_connections "$mem_mb" "$cores")"
    log_file_mb="$(calc_log_file_mb "$pool_mb")"
    io_capacity=$((cores * 100))
    if (( io_capacity < 200 )); then
        io_capacity=200
    elif (( io_capacity > 800 )); then
        io_capacity=800
    fi
    io_capacity_max=$((io_capacity * 2))

    if [[ -n "$output" ]]; then
        generate_config "$mem_mb" "$cores" "$pool_mb" "$pool_instances" "$max_connections" "$log_file_mb" "$io_capacity" "$io_capacity_max" > "$output"
    else
        generate_config "$mem_mb" "$cores" "$pool_mb" "$pool_instances" "$max_connections" "$log_file_mb" "$io_capacity" "$io_capacity_max"
    fi
}

main "$@"
