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

JSON=0
ARGS=()

for arg in "$@"; do
  case "$arg" in
    --json)
      JSON=1
    ;;
    *)
      ARGS+=("$arg")
    ;;
  esac
done

COMMAND="${ARGS[0]:-}"
if [ "${#ARGS[@]}" -gt 0 ]; then
  ARGS=("${ARGS[@]:1}")
fi

SITES_DIR="/etc/sone/sites"
SSL_LOCAL_DIR="/etc/sone/ssl/local"
NGINX_SITES_DIR="/etc/nginx/conf.d"
DEFAULT_USER="sone"
DB_PROVIDER_FILE="/etc/sone/database/provider"
SYSTEM_DIR="/etc/sone/system"
SYSTEM_DOMAIN_FILE="/etc/sone/system/domain"
DOMAINS_DIR="/etc/sone/domains"
SITE_DOMAINS_DIR="/etc/sone/site-domains"

usage() {
  cat <<'HELP'
sOne CLI

Usage:
  sone php:list [--json]
  sone php:default --php=8.5 [--json]

  sone user:create --user=sone [--password=secret] [--shell=/bin/bash] [--json]
  sone user:delete --user=sone --force [--json]

  # Default user has SSH + SFTP access.
  # This command restricts user to SFTP only.
  sone user:sftp:only --user=sone [--json]

  # Restores normal SSH + SFTP access.
  sone user:sftp:off --user=sone [--json]

  # Backward compatibility alias for user:sftp:only.
  sone user:sftp:on --user=sone [--json]

  sone site:list [--json]
  sone site:create --domain=site1.com --user=sone --php=8.5 --root=public [--db=1 --db-type=mariadb --db-name=site_db --db-user=site_user --db-password=secret] [--json]
  sone site:add:php --domain=site1.com --user=sone --php=8.5 --root=public [--db=1 --db-type=mariadb --db-name=site_db --db-user=site_user --db-password=secret] [--json]
  sone site:delete --domain=site1.com --force [--json]
  sone site:php:switch --domain=site1.com --php=8.4 [--json]

  sone site:local-domain:add --domain=site1.com --local-domain=site1.lan [--json]

  sone system:domain:set --domain=s01.one [--json]
  sone system:domain:show [--json]

  sone user:system-domain:create --user=sone --user-id=12345 [--json]

  sone domain:list [--user=sone] [--user-id=12345] [--json]
  sone domain:add --user=sone --user-id=12345 --domain=example.com [--type=custom|system|preview] [--json]
  sone domain:show --domain=example.com [--json]
  sone domain:delete --domain=example.com --force [--json]

  sone site:domain:attach --site=site1.com --domain=example.com [--behavior=serve|redirect_to_primary|disabled] [--primary=0] [--ssl=0] [--email=admin@example.com] [--json]
  sone site:domain:detach --site=site1.com --domain=example.com --force [--json]
  sone site:domain:primary --site=site1.com --domain=example.com [--redirect-preview=1] [--json]
  sone domain:transfer --domain=example.com --user=newuser --user-id=777 [--json]
  sone site:transfer --site=site1.com --user=newuser --user-id=777 [--move-files=1] [--update-domains=1] [--json]

  sone site:ssl:issue --domain=site1.com --email=admin@example.com [--www=1] [--staging=0] [--json]
  sone site:ssl:local --domain=site1.com --local-domain=site1.lan [--json]
  sone site:ssl:wildcard --domain=example.com --email=admin@example.com

  sone service:reload --name=nginx [--json]

  sone cache:clear --service=nginx [--json]
  sone cache:clear --service=varnish [--json]
  sone cache:clear --service=pagespeed [--json]
  sone cache:clear --service=redis --force [--json]
  sone cache:clear --service=redis --all-databases --force [--json]
  sone cache:clear --service=all --force [--json]
  
  sone db:list [--type=mariadb|mysql|postgresql] [--json]
  sone db:create --type=mariadb --db=site_db [--charset=utf8mb4] [--collate=utf8mb4_unicode_ci] [--json]
  sone db:delete --type=mariadb --db=site_db --force [--json]
  sone db:user:create --type=mariadb --db=site_db --user=site_user --password=secret [--host=localhost] [--json]
  sone db:user:delete --type=mariadb --user=site_user [--host=localhost] --force [--json]
  
  sone node:list --user=sone [--json]
  sone node:install --user=sone --version=22 [--json]
  sone node:default --user=sone --version=22 [--json]
  sone node:remove --user=sone --version=22 [--json]
  sone bun:install --user=sone [--json]
  
  sone packages:list [--json]
  sone package:show --name=sone-redis [--json]
  
  sone powerdns:configure --role=primary|secondary --backend=sqlite|mysql --master-ip=1.1.1.1 --secondary-ip=2.2.2.2 --api-key=secret [--mysql-password=secret] [--json]
  sone powerdns:status [--json]

  sone dns:zone:list [--json]
  sone dns:zone:show --zone=example.com [--json]
  sone dns:zone:create --zone=example.com [--kind=primary|secondary] [--master-ip=1.1.1.1] [--json]
  sone dns:zone:delete --zone=example.com --force [--json]
  sone dns:record:add --zone=example.com --name=www --type=A --content=1.2.3.4 [--ttl=3600] [--json]
  sone dns:record:replace --zone=example.com --name=www --type=A --content=1.2.3.4 [--ttl=3600] [--json]
  sone dns:record:delete --zone=example.com --name=www --type=A --force [--json]
  sone dns:record:list --zone=example.com [--json]
  
  sone firewall:status [--json]
  sone firewall:enable [--json]
  sone firewall:disable [--json]
  sone firewall:reload [--json]
  sone firewall:allow --port=8080 [--proto=tcp] [--json]
  sone firewall:delete --port=8080 [--proto=tcp] [--json]

  sone fail2ban:status [--json]
  sone fail2ban:jails [--json]
  sone fail2ban:jail --name=sshd [--json]
  sone fail2ban:ban --jail=sshd --ip=1.2.3.4 [--json]
  sone fail2ban:unban --jail=sshd --ip=1.2.3.4 [--json]

  sone antivirus:status [--json]
  sone antivirus:socket [--json]
  sone antivirus:scan --path=/home/sone/htdocs/site.com [--json]
  sone antivirus:update [--json]
  
  sone backup:create --user=testuser --type=files --domain=example.com [--json]
  sone backup:create --user=testuser --type=mysql --database=db_name [--json]
  sone backup:create --user=testuser --type=postgresql --database=db_name [--json]
  sone backup:create --user=testuser --type=sqlite --file=/path/db.sqlite [--json]

  sone backup:list --user=testuser [--json]

  sone backup:delete --user=testuser --file=files/example.tar.gz [--json]

  sone backup:restore --user=testuser --type=files --domain=example.com --file=files/example.tar.gz [--json]
  sone backup:restore --user=testuser --type=mysql --database=db_name --file=databases/db.sql.gz [--json]
  sone backup:restore --user=testuser --type=postgresql --database=db_name --file=databases/db.sql.gz [--json]
  sone backup:restore --user=testuser --type=sqlite --target=/path/db.sqlite --file=databases/db.sqlite.gz [--json]
  
  sone logs:list [--json]
  sone logs:read --file=nginx/error.log [--lines=200] [--json]
  sone logs:tail --file=nginx/error.log [--lines=100] [--json]
  
  sone mail:status [--json]
  sone mail:queue [--json]
  sone mail:test --to=email@example.com [--from=noreply@example.com] [--subject="Test"] [--body="Hello"] [--json]
  sone mail:relay:set --host=smtp.example.com --port=587 --username=user --password=secret [--security=tls] [--json]
  sone mail:relay:disable [--json]
  
  sone cron:status [--json]
  sone cron:list [--json]
  sone cron:create --name=test --command='echo ok' [--json]
  sone cron:delete --name=test [--json]
  
  sone system:status [--json]
  sone services:status [--json]
  sone disk:status [--json]
  sone memory:status [--json]
  sone cpu:status [--json]
  sone ports:list [--json]
  
  sone queue:status [--json]
  sone queue:restart [--json]
  sone queue:logs [--lines=100] [--json]
  
  sone limits:status --user=testuser [--json]
  sone limits:usage --user=testuser [--json]
  sone limits:set --user=testuser [--cpu=50] [--memory=512M] [--tasks=100] [--disk=10G] [--sites=5] [--domains=10] [--databases=5] [--emails=20] [--cron-jobs=10] [--backups=10] [--json]
  sone limits:check --user=testuser --resource=sites [--json]
  
  sone ssl:list [--json]
  sone ssl:status --domain=example.com [--json]
  sone ssl:renew [--type=acme|local] [--domain=example.com] [--json]
  sone ssl:local:create --domain=example.local [--days=3650] [--json]
  sone ssl:local:renew --domain=example.local [--days=3650] [--json]
  sone ssl:local:delete --domain=example.local [--json]
  sone ssl:logs [--lines=100] [--json]

  sone app:install --app=nuxt --version=4 --user=sone --domain=nuxt-test.s01.one [--ui=none] [--node=auto] [--port=auto] [--json]
  
  sone app:service:create --user=sone --service=nuxt --template=nuxt-nitro [--json]
  sone app:service:start --user=sone --service=nuxt [--json]
  sone app:service:stop --user=sone --service=nuxt [--json]
  sone app:service:restart --user=sone --service=nuxt [--json]
  sone app:service:status --user=sone --service=nuxt [--json]
  sone app:service:logs --user=sone --service=nuxt [--lines=100] [--json]
  
  sone app:list [--json]
  sone app:versions --app=nuxt [--json]
  sone app:defaults --app=nuxt --version=4 [--json]
  
  sone app:port:allocate --user=sone --domain=test.s01.one [--json]
  sone app:render:nginx --app=nuxt --user=sone --domain=test.s01.one --port=3003 [--json]
  sone app:render:service --app=nuxt --user=sone --domain=test.s01.one --port=3003 [--json]
  
  sone about [--json]
  sone license:show [--json]
  sone db:root:show --type=mysql|mariadb|postgresql [--json]
  
  sone nginx:test [--json]
  sone nginx:reload [--json]

  sone system:permissions:reset --path=/home/sone/htdocs/site1.com --directories=755 --files=644 [--json]
HELP
}

json_escape() {
  local value="${1:-}"

  value="${value//\\/\\\\}"
  value="${value//\"/\\\"}"
  value="${value//$'\n'/\\n}"
  value="${value//$'\r'/\\r}"
  value="${value//$'\t'/\\t}"

  printf '%s' "$value"
}

out_error() {
  local message="${1:-ERROR}"
  local code="${2:-1}"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":false,"error":"%s","code":%s}\n' "$(json_escape "$message")" "$code" >&2
  else
    echo "ERROR: $message" >&2
  fi

  exit "$code"
}

get_arg() {
  local name="${1:-}"
  local default="${2:-}"
  shift 2 || true

  if [ -z "$name" ]; then
    out_error "get_arg name is required"
  fi

  local arg
  for arg in "$@"; do
    case "$arg" in
      "${name}="*)
        echo "${arg#*=}"
        return 0
      ;;
    esac
  done

  echo "$default"
}

has_flag() {
  local flag="${1:-}"
  shift || true

  if [ -z "$flag" ]; then
    return 1
  fi

  local arg
  for arg in "$@"; do
    if [ "$arg" = "$flag" ]; then
      return 0
    fi
  done

  return 1
}

require_root() {
  if [ "$(id -u)" -ne 0 ]; then
    out_error "this command must be run as root"
  fi
}

require_command() {
  local name="${1:-}"

  if [ -z "$name" ]; then
    out_error "required command name is empty"
  fi

  if ! command -v "$name" >/dev/null 2>&1; then
    out_error "required command not found: $name"
  fi
}

normalize_domain() {
  local domain="${1:-}"

  if [ -z "$domain" ]; then
    out_error "--domain is required"
  fi

  if ! echo "$domain" | grep -Eq '^[A-Za-z0-9.-]+$'; then
    out_error "invalid domain: $domain"
  fi

  echo "$domain" | tr '[:upper:]' '[:lower:]'
}

normalize_local_domain() {
  local domain="${1:-}"

  if [ -z "$domain" ]; then
    out_error "--local-domain is required"
  fi

  if ! echo "$domain" | grep -Eq '^[A-Za-z0-9.-]+$'; then
    out_error "invalid local domain: $domain"
  fi

  echo "$domain" | tr '[:upper:]' '[:lower:]'
}

normalize_user() {
  local user="${1:-}"

  if [ -z "$user" ]; then
    out_error "--user is required"
  fi

  if ! echo "$user" | grep -Eq '^[a-z_][a-z0-9_-]*[$]?$'; then
    out_error "invalid linux user: $user"
  fi

  echo "$user"
}

ensure_user_exists() {
  local user="${1:-}"
  local shell="${2:-/bin/bash}"

  if [ -z "$user" ]; then
    out_error "user is required"
  fi

  if ! id "$user" >/dev/null 2>&1; then
    useradd -m -s "$shell" "$user"
  fi
}

ensure_nginx_ok_and_reload() {
  require_command nginx
  nginx -t
  systemctl reload nginx
}

site_file_path() {
  local domain="${1:-}"

  if [ -z "$domain" ]; then
    out_error "domain is required"
  fi

  echo "${SITES_DIR}/${domain}.conf"
}

get_site_value() {
  local domain="${1:-}"
  local key="${2:-}"
  local file

  if [ -z "$domain" ]; then
    out_error "domain is required"
  fi

  if [ -z "$key" ]; then
    out_error "site metadata key is required"
  fi

  file="$(site_file_path "$domain")"

  if [ ! -f "$file" ]; then
    out_error "site metadata not found: $file"
  fi

  grep -E "^${key}=" "$file" | head -n1 | cut -d= -f2- | sed -E 's/^"//; s/"$//'
}

php_list() {
  local versions=()

  if [ -d /opt/php ]; then
    while IFS= read -r version; do
      versions+=("$version")
    done < <(ls -1 /opt/php 2>/dev/null | grep -E '^[0-9]+\.[0-9]+$' || true)
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"php_versions":['

    local first=1
    local version

    for version in "${versions[@]}"; do
      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '"%s"' "$(json_escape "$version")"
      first=0
    done

    printf ']}\n'
  else
    if [ "${#versions[@]}" -eq 0 ]; then
      echo "No PHP versions found"
      return 0
    fi

    printf '%s\n' "${versions[@]}"
  fi
}

php_default() {
  require_root

  local php_version
  php_version="$(get_arg "--php" "" "${ARGS[@]}")"

  if [ -z "$php_version" ]; then
    out_error "--php is required"
  fi

  if [ ! -x "/opt/php/${php_version}/bin/php" ]; then
    out_error "PHP binary not found: /opt/php/${php_version}/bin/php"
  fi

  update-alternatives --set php "/opt/php/${php_version}/bin/php" >/dev/null

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"default PHP changed","php":"%s"}\n' "$(json_escape "$php_version")"
  else
    php -v
  fi
}

user_create() {
  require_root

  local user password shell

  user="$(normalize_user "$(get_arg "--user" "" "${ARGS[@]}")")"
  password="$(get_arg "--password" "" "${ARGS[@]}")"
  shell="$(get_arg "--shell" "/bin/bash" "${ARGS[@]}")"

  if id "$user" >/dev/null 2>&1; then
    out_error "user already exists: $user"
  fi

  useradd -m -s "$shell" "$user"

  if [ -n "$password" ]; then
    echo "${user}:${password}" | chpasswd
  fi

  mkdir -p "/home/${user}/htdocs"
  chown -R "${user}:${user}" "/home/${user}/htdocs"
  chmod 711 "/home/${user}"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"user created","user":"%s","home":"/home/%s","access":"ssh+sftp"}\n' \
      "$(json_escape "$user")" \
      "$(json_escape "$user")"
  else
    echo "User created: $user"
    echo "Home: /home/$user"
    echo "Access: SSH + SFTP"
  fi
}

user_delete() {
  require_root

  local user

  user="$(normalize_user "$(get_arg "--user" "" "${ARGS[@]}")")"

  if ! id "$user" >/dev/null 2>&1; then
    out_error "user not found: $user"
  fi

  if ! has_flag "--force" "${ARGS[@]}"; then
    out_error "--force is required"
  fi

  userdel -r "$user"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"user deleted","user":"%s"}\n' "$(json_escape "$user")"
  else
    echo "User deleted: $user"
  fi
}

user_sftp_only() {
  require_root

  local user

  user="$(normalize_user "$(get_arg "--user" "" "${ARGS[@]}")")"

  if ! id "$user" >/dev/null 2>&1; then
    out_error "user not found: $user"
  fi

  if ! getent group sone-sftp >/dev/null 2>&1; then
    groupadd --system sone-sftp
  fi

  usermod -aG sone-sftp "$user"
  usermod -s /usr/sbin/nologin "$user"

  mkdir -p "/home/${user}/htdocs"
  chown -R "${user}:${user}" "/home/${user}/htdocs"
  chmod 711 "/home/${user}"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"sftp-only enabled","user":"%s","group":"sone-sftp","access":"sftp-only"}\n' "$(json_escape "$user")"
  else
    echo "SFTP-only enabled for user: $user"
    echo "Group: sone-sftp"
    echo "Access: SFTP only"
  fi
}

user_sftp_off() {
  require_root

  local user

  user="$(normalize_user "$(get_arg "--user" "" "${ARGS[@]}")")"

  if ! id "$user" >/dev/null 2>&1; then
    out_error "user not found: $user"
  fi

  usermod -s /bin/bash "$user"

  if getent group sone-sftp >/dev/null 2>&1; then
    gpasswd -d "$user" sone-sftp >/dev/null 2>&1 || true
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"sftp-only disabled","user":"%s","access":"ssh+sftp"}\n' "$(json_escape "$user")"
  else
    echo "SFTP-only disabled for user: $user"
    echo "Access: SSH + SFTP"
  fi
}


domain_safe_name() {
  local domain="${1:-}"

  if [ -z "$domain" ]; then
    out_error "domain is required"
  fi

  normalize_domain "$domain"
}

domain_file_path() {
  local domain
  domain="$(domain_safe_name "${1:-}")"
  echo "${DOMAINS_DIR}/${domain}.conf"
}

site_domain_file_path() {
  local site domain
  site="$(domain_safe_name "${1:-}")"
  domain="$(domain_safe_name "${2:-}")"

  mkdir -p "${SITE_DOMAINS_DIR}/${site}"
  echo "${SITE_DOMAINS_DIR}/${site}/${domain}.conf"
}

system_domain_get() {
  if [ ! -f "$SYSTEM_DOMAIN_FILE" ]; then
    out_error "system domain is not configured. Run: sone system:domain:set --domain=s01.one"
  fi

  cat "$SYSTEM_DOMAIN_FILE" | tr -d '[:space:]'
}

system_domain_set() {
  require_root

  local domain
  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"

  mkdir -p "$SYSTEM_DIR"
  printf '%s\n' "$domain" > "$SYSTEM_DOMAIN_FILE"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"system domain saved","domain":"%s"}\n' \
      "$(json_escape "$domain")"
  else
    echo "System domain saved: $domain"
  fi
}

system_domain_show() {
  local domain=""

  if [ -f "$SYSTEM_DOMAIN_FILE" ]; then
    domain="$(cat "$SYSTEM_DOMAIN_FILE" | tr -d '[:space:]')"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"domain":"%s","configured":%s}\n' \
      "$(json_escape "$domain")" \
      "$([ -n "$domain" ] && echo true || echo false)"
  else
    if [ -n "$domain" ]; then
      echo "$domain"
    else
      echo "System domain is not configured"
    fi
  fi
}

domain_write_meta() {
  local domain="${1:-}"
  local user="${2:-}"
  local user_id="${3:-}"
  local type="${4:-custom}"
  local status="${5:-active}"
  local ssl_status="${6:-none}"
  local file

  domain="$(domain_safe_name "$domain")"
  file="$(domain_file_path "$domain")"

  mkdir -p "$DOMAINS_DIR"

  cat > "$file" <<EOF_DOMAIN
DOMAIN="${domain}"
USER="${user}"
USER_ID="${user_id}"
TYPE="${type}"
STATUS="${status}"
SSL_STATUS="${ssl_status}"
CREATED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
EOF_DOMAIN
}

domain_get_value() {
  local domain="${1:-}"
  local key="${2:-}"
  local file

  file="$(domain_file_path "$domain")"

  if [ ! -f "$file" ]; then
    out_error "domain metadata not found: $domain"
  fi

  grep -E "^${key}=" "$file" | head -n1 | cut -d= -f2- | sed -E 's/^"//; s/"$//'
}

domain_add() {
  require_root

  local domain user user_id type status ssl_status file

  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"
  user="$(get_arg "--user" "" "${ARGS[@]}")"
  user_id="$(get_arg "--user-id" "" "${ARGS[@]}")"
  type="$(get_arg "--type" "custom" "${ARGS[@]}")"
  status="active"
  ssl_status="none"

  case "$type" in
    system|preview|custom)
      true
    ;;
    *)
      out_error "invalid --type. Use system, preview or custom"
    ;;
  esac

  if [ -n "$user" ]; then
    user="$(normalize_user "$user")"
  fi

  if [ -n "$user" ] && ! id "$user" >/dev/null 2>&1; then
    out_error "user not found: $user"
  fi

  file="$(domain_file_path "$domain")"

  if [ -f "$file" ]; then
    out_error "domain already exists: $domain"
  fi

  domain_write_meta "$domain" "$user" "$user_id" "$type" "$status" "$ssl_status"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"domain added","domain":"%s","user":"%s","user_id":"%s","type":"%s","status":"%s","ssl_status":"%s"}\n' \
      "$(json_escape "$domain")" \
      "$(json_escape "$user")" \
      "$(json_escape "$user_id")" \
      "$(json_escape "$type")" \
      "$(json_escape "$status")" \
      "$(json_escape "$ssl_status")"
  else
    echo "Domain added: $domain"
    echo "User: $user"
    echo "User ID: $user_id"
    echo "Type: $type"
  fi
}

domain_show() {
  local domain file user user_id type status ssl_status

  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"
  file="$(domain_file_path "$domain")"

  if [ ! -f "$file" ]; then
    out_error "domain not found: $domain"
  fi

  user="$(domain_get_value "$domain" "USER")"
  user_id="$(domain_get_value "$domain" "USER_ID")"
  type="$(domain_get_value "$domain" "TYPE")"
  status="$(domain_get_value "$domain" "STATUS")"
  ssl_status="$(domain_get_value "$domain" "SSL_STATUS")"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"domain":{"domain":"%s","user":"%s","user_id":"%s","type":"%s","status":"%s","ssl_status":"%s"}}\n' \
      "$(json_escape "$domain")" \
      "$(json_escape "$user")" \
      "$(json_escape "$user_id")" \
      "$(json_escape "$type")" \
      "$(json_escape "$status")" \
      "$(json_escape "$ssl_status")"
  else
    echo "Domain: $domain"
    echo "User: $user"
    echo "User ID: $user_id"
    echo "Type: $type"
    echo "Status: $status"
    echo "SSL: $ssl_status"
  fi
}

domain_list() {
  local filter_user filter_user_id files first file domain user user_id type status ssl_status

  mkdir -p "$DOMAINS_DIR"

  filter_user="$(get_arg "--user" "" "${ARGS[@]}")"
  filter_user_id="$(get_arg "--user-id" "" "${ARGS[@]}")"

  if [ -n "$filter_user" ]; then
    filter_user="$(normalize_user "$filter_user")"
  fi

  files=()

  if ls "$DOMAINS_DIR"/*.conf >/dev/null 2>&1; then
    while IFS= read -r file; do
      files+=("$file")
    done < <(ls -1 "$DOMAINS_DIR"/*.conf)
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"domains":['
    first=1

    for file in "${files[@]}"; do
      domain="$(basename "$file" .conf)"
      user="$(grep -E '^USER=' "$file" | head -n1 | cut -d= -f2- | sed -E 's/^"//; s/"$//')"
      user_id="$(grep -E '^USER_ID=' "$file" | head -n1 | cut -d= -f2- | sed -E 's/^"//; s/"$//')"
      type="$(grep -E '^TYPE=' "$file" | head -n1 | cut -d= -f2- | sed -E 's/^"//; s/"$//')"
      status="$(grep -E '^STATUS=' "$file" | head -n1 | cut -d= -f2- | sed -E 's/^"//; s/"$//')"
      ssl_status="$(grep -E '^SSL_STATUS=' "$file" | head -n1 | cut -d= -f2- | sed -E 's/^"//; s/"$//')"

      if [ -n "$filter_user" ] && [ "$user" != "$filter_user" ]; then
        continue
      fi

      if [ -n "$filter_user_id" ] && [ "$user_id" != "$filter_user_id" ]; then
        continue
      fi

      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '{"domain":"%s","user":"%s","user_id":"%s","type":"%s","status":"%s","ssl_status":"%s"}' \
        "$(json_escape "$domain")" \
        "$(json_escape "$user")" \
        "$(json_escape "$user_id")" \
        "$(json_escape "$type")" \
        "$(json_escape "$status")" \
        "$(json_escape "$ssl_status")"

      first=0
    done

    printf ']}\n'
  else
    for file in "${files[@]}"; do
      domain="$(basename "$file" .conf)"
      user="$(grep -E '^USER=' "$file" | head -n1 | cut -d= -f2- | sed -E 's/^"//; s/"$//')"
      user_id="$(grep -E '^USER_ID=' "$file" | head -n1 | cut -d= -f2- | sed -E 's/^"//; s/"$//')"

      if [ -n "$filter_user" ] && [ "$user" != "$filter_user" ]; then
        continue
      fi

      if [ -n "$filter_user_id" ] && [ "$user_id" != "$filter_user_id" ]; then
        continue
      fi

      echo "$domain"
    done
  fi
}

domain_delete() {
  require_root

  local domain file

  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"
  file="$(domain_file_path "$domain")"

  if [ ! -f "$file" ]; then
    out_error "domain not found: $domain"
  fi

  if ! has_flag "--force" "${ARGS[@]}"; then
    out_error "--force is required"
  fi

  rm -f "$file"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"domain deleted","domain":"%s"}\n' \
      "$(json_escape "$domain")"
  else
    echo "Domain deleted: $domain"
  fi
}

user_system_domain_create() {
  require_root

  local user user_id system_domain domain

  user="$(normalize_user "$(get_arg "--user" "" "${ARGS[@]}")")"
  user_id="$(get_arg "--user-id" "" "${ARGS[@]}")"

  if [ -z "$user_id" ]; then
    out_error "--user-id is required"
  fi

  if ! id "$user" >/dev/null 2>&1; then
    out_error "user not found: $user"
  fi

  system_domain="$(system_domain_get)"
  domain="u${user_id}.${system_domain}"

  if [ ! -f "$(domain_file_path "$domain")" ]; then
    domain_write_meta "$domain" "$user" "$user_id" "system" "active" "none"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"user system domain created","domain":"%s","user":"%s","user_id":"%s","system_domain":"%s"}\n' \
      "$(json_escape "$domain")" \
      "$(json_escape "$user")" \
      "$(json_escape "$user_id")" \
      "$(json_escape "$system_domain")"
  else
    echo "$domain"
  fi
}

site_domain_render_nginx() {
  local site domain behavior primary php_version docroot socket conf_file primary_domain

  site="$(domain_safe_name "${1:-}")"
  domain="$(domain_safe_name "${2:-}")"
  behavior="${3:-serve}"
  primary="${4:-0}"

  php_version="$(get_site_value "$site" "PHP_VERSION")"
  docroot="$(get_site_value "$site" "DOCROOT")"

  socket="/run/sone-php/php${php_version}-fpm.sock"

  if [ ! -S "$socket" ]; then
    out_error "PHP-FPM socket not found: $socket"
  fi

  conf_file="${NGINX_SITES_DIR}/sone-${site}-domain-${domain}.conf"

  if [ "$behavior" = "redirect_to_primary" ]; then
    primary_domain="$(site_domain_primary_get "$site")"

    if [ -z "$primary_domain" ]; then
      primary_domain="$site"
    fi

    cat > "$conf_file" <<EOF_NGINX
server {
    listen 80;
    listen [::]:80;

    server_name ${domain};

    return 301 https://${primary_domain}\$request_uri;
}
EOF_NGINX
  elif [ "$behavior" = "disabled" ]; then
    cat > "$conf_file" <<EOF_NGINX
server {
    listen 80;
    listen [::]:80;

    server_name ${domain};

    return 404;
}
EOF_NGINX
  else
    cat > "$conf_file" <<EOF_NGINX
server {
    listen 80;
    listen [::]:80;

    server_name ${domain};

    root ${docroot};
    index index.php index.html index.htm;

    access_log /var/log/nginx/${domain}.access.log;
    error_log /var/log/nginx/${domain}.error.log;

    location / {
        try_files \$uri \$uri/ /index.php?\$query_string;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_index index.php;
        fastcgi_pass unix:${socket};
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT \$document_root;
    }

    location ~ /\. {
        deny all;
    }
}
EOF_NGINX
  fi

  echo "$conf_file"
}

site_domain_primary_get() {
  local site file domain is_primary

  site="$(domain_safe_name "${1:-}")"

  if [ ! -d "${SITE_DOMAINS_DIR}/${site}" ]; then
    echo ""
    return 0
  fi

  if ls "${SITE_DOMAINS_DIR}/${site}"/*.conf >/dev/null 2>&1; then
    while IFS= read -r file; do
      domain="$(basename "$file" .conf)"
      is_primary="$(grep -E '^IS_PRIMARY=' "$file" | head -n1 | cut -d= -f2- | sed -E 's/^"//; s/"$//')"

      if [ "$is_primary" = "1" ]; then
        echo "$domain"
        return 0
      fi
    done < <(ls -1 "${SITE_DOMAINS_DIR}/${site}"/*.conf)
  fi

  echo ""
}

site_domain_write_meta() {
  local site domain behavior is_primary type file

  site="$(domain_safe_name "${1:-}")"
  domain="$(domain_safe_name "${2:-}")"
  behavior="${3:-serve}"
  is_primary="${4:-0}"
  type="${5:-custom}"

  file="$(site_domain_file_path "$site" "$domain")"

  cat > "$file" <<EOF_SITE_DOMAIN
SITE="${site}"
DOMAIN="${domain}"
BEHAVIOR="${behavior}"
IS_PRIMARY="${is_primary}"
TYPE="${type}"
CREATED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
EOF_SITE_DOMAIN
}

site_domain_attach() {
  require_root

  local site domain behavior primary ssl email type user user_id domain_file conf_file

  site="$(normalize_domain "$(get_arg "--site" "" "${ARGS[@]}")")"
  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"
  behavior="$(get_arg "--behavior" "serve" "${ARGS[@]}")"
  primary="$(get_arg "--primary" "0" "${ARGS[@]}")"
  ssl="$(get_arg "--ssl" "0" "${ARGS[@]}")"
  email="$(get_arg "--email" "" "${ARGS[@]}")"
  type="$(get_arg "--type" "custom" "${ARGS[@]}")"

  case "$behavior" in
    serve|redirect_to_primary|disabled)
      true
    ;;
    *)
      out_error "invalid --behavior. Use serve, redirect_to_primary or disabled"
    ;;
  esac

  case "$primary" in
    1|true|yes)
      primary="1"
    ;;
    0|false|no)
      primary="0"
    ;;
    *)
      out_error "invalid --primary. Use 1 or 0"
    ;;
  esac

  if [ ! -f "$(site_file_path "$site")" ]; then
    out_error "site not found: $site"
  fi

  domain_file="$(domain_file_path "$domain")"

  if [ ! -f "$domain_file" ]; then
    user="$(get_site_value "$site" "USER")"
    user_id="$(get_arg "--user-id" "" "${ARGS[@]}")"
    domain_write_meta "$domain" "$user" "$user_id" "$type" "active" "none"
  fi

  if [ "$primary" = "1" ]; then
    site_domain_clear_primary "$site"
  fi

  site_domain_write_meta "$site" "$domain" "$behavior" "$primary" "$type"

  conf_file="$(site_domain_render_nginx "$site" "$domain" "$behavior" "$primary")"

  ensure_nginx_ok_and_reload

  case "$ssl" in
    1|true|yes)
      if [ -z "$email" ]; then
        out_error "--email is required when --ssl=1"
      fi

      ARGS=("--domain=${domain}" "--email=${email}" "--www=0")
      site_ssl_issue
    ;;
    0|false|no)
      true
    ;;
    *)
      out_error "invalid --ssl. Use 1 or 0"
    ;;
  esac

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"domain attached to site","site":"%s","domain":"%s","behavior":"%s","primary":"%s","nginx_config":"%s"}\n' \
      "$(json_escape "$site")" \
      "$(json_escape "$domain")" \
      "$(json_escape "$behavior")" \
      "$(json_escape "$primary")" \
      "$(json_escape "$conf_file")"
  else
    echo "Domain attached"
    echo "Site: $site"
    echo "Domain: $domain"
    echo "Behavior: $behavior"
    echo "Primary: $primary"
    echo "Nginx: $conf_file"
  fi
}

site_domain_clear_primary() {
  local site file

  site="$(domain_safe_name "${1:-}")"

  if [ ! -d "${SITE_DOMAINS_DIR}/${site}" ]; then
    return 0
  fi

  if ls "${SITE_DOMAINS_DIR}/${site}"/*.conf >/dev/null 2>&1; then
    while IFS= read -r file; do
      sed -i -E 's/^IS_PRIMARY=.*/IS_PRIMARY="0"/' "$file"
    done < <(ls -1 "${SITE_DOMAINS_DIR}/${site}"/*.conf)
  fi
}

site_domain_detach() {
  require_root

  local site domain file conf_file

  site="$(normalize_domain "$(get_arg "--site" "" "${ARGS[@]}")")"
  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"

  if ! has_flag "--force" "${ARGS[@]}"; then
    out_error "--force is required"
  fi

  file="$(site_domain_file_path "$site" "$domain")"
  conf_file="${NGINX_SITES_DIR}/sone-${site}-domain-${domain}.conf"

  rm -f "$file" "$conf_file"

  ensure_nginx_ok_and_reload

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"domain detached from site","site":"%s","domain":"%s"}\n' \
      "$(json_escape "$site")" \
      "$(json_escape "$domain")"
  else
    echo "Domain detached"
    echo "Site: $site"
    echo "Domain: $domain"
  fi
}


domain_transfer() {
  require_root

  local domain user user_id file

  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"
  user="$(normalize_user "$(get_arg "--user" "" "${ARGS[@]}")")"
  user_id="$(get_arg "--user-id" "" "${ARGS[@]}")"

  if [ -z "$user_id" ]; then
    out_error "--user-id is required"
  fi

  if ! id "$user" >/dev/null 2>&1; then
    out_error "target user not found: $user"
  fi

  file="$(domain_file_path "$domain")"

  if [ ! -f "$file" ]; then
    out_error "domain not found: $domain"
  fi

  sed -i -E "s/^USER=.*/USER=\"${user}\"/" "$file"
  sed -i -E "s/^USER_ID=.*/USER_ID=\"${user_id}\"/" "$file"

  if ! grep -q '^UPDATED_AT=' "$file"; then
    echo "UPDATED_AT=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"" >> "$file"
  else
    sed -i -E "s/^UPDATED_AT=.*/UPDATED_AT=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"/" "$file"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"domain transferred","domain":"%s","user":"%s","user_id":"%s"}\n' \
      "$(json_escape "$domain")" \
      "$(json_escape "$user")" \
      "$(json_escape "$user_id")"
  else
    echo "Domain transferred: $domain -> $user"
  fi
}

site_transfer() {
  require_root

  local site new_user new_user_id move_files update_domains site_file old_user old_docroot new_base new_docroot
  local file domain conf_file php_version socket

  site="$(normalize_domain "$(get_arg "--site" "" "${ARGS[@]}")")"
  new_user="$(normalize_user "$(get_arg "--user" "" "${ARGS[@]}")")"
  new_user_id="$(get_arg "--user-id" "" "${ARGS[@]}")"
  move_files="$(get_arg "--move-files" "1" "${ARGS[@]}")"
  update_domains="$(get_arg "--update-domains" "1" "${ARGS[@]}")"

  if [ -z "$new_user_id" ]; then
    out_error "--user-id is required"
  fi

  if ! id "$new_user" >/dev/null 2>&1; then
    out_error "target user not found: $new_user"
  fi

  site_file="$(site_file_path "$site")"

  if [ ! -f "$site_file" ]; then
    out_error "site not found: $site"
  fi

  old_user="$(get_site_value "$site" "USER")"
  old_docroot="$(get_site_value "$site" "DOCROOT")"

  new_base="/home/${new_user}/htdocs/${site}"
  new_docroot="${new_base}/public"

  case "$move_files" in
    1|true|yes)
      mkdir -p "/home/${new_user}/htdocs"

      if [ -d "/home/${old_user}/htdocs/${site}" ]; then
        mv "/home/${old_user}/htdocs/${site}" "$new_base"
      elif [ ! -d "$new_base" ]; then
        mkdir -p "$new_docroot"
      fi

      chown -R "${new_user}:${new_user}" "$new_base" 2>/dev/null || true
    ;;
    0|false|no)
      new_docroot="$old_docroot"
    ;;
    *)
      out_error "invalid --move-files. Use 1 or 0"
    ;;
  esac

  sed -i -E "s/^USER=.*/USER=\"${new_user}\"/" "$site_file"

  if grep -q '^DOCROOT=' "$site_file"; then
    sed -i -E "s#^DOCROOT=.*#DOCROOT=\"${new_docroot}\"#" "$site_file"
  fi

  if ! grep -q '^USER_ID=' "$site_file"; then
    echo "USER_ID=\"${new_user_id}\"" >> "$site_file"
  else
    sed -i -E "s/^USER_ID=.*/USER_ID=\"${new_user_id}\"/" "$site_file"
  fi

  if ! grep -q '^UPDATED_AT=' "$site_file"; then
    echo "UPDATED_AT=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"" >> "$site_file"
  else
    sed -i -E "s/^UPDATED_AT=.*/UPDATED_AT=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"/" "$site_file"
  fi

  case "$update_domains" in
    1|true|yes)
      if [ -d "${SITE_DOMAINS_DIR}/${site}" ] && ls "${SITE_DOMAINS_DIR}/${site}"/*.conf >/dev/null 2>&1; then
        while IFS= read -r file; do
          domain="$(basename "$file" .conf)"

          if [ -f "$(domain_file_path "$domain")" ]; then
            ARGS=("--domain=${domain}" "--user=${new_user}" "--user-id=${new_user_id}")
            domain_transfer >/dev/null
          fi
        done < <(ls -1 "${SITE_DOMAINS_DIR}/${site}"/*.conf)
      fi
    ;;
    0|false|no)
      true
    ;;
    *)
      out_error "invalid --update-domains. Use 1 or 0"
    ;;
  esac

  php_version="$(get_site_value "$site" "PHP_VERSION")"
  socket="/run/sone-php/php${php_version}-fpm.sock"

  conf_file="${NGINX_SITES_DIR}/sone-${site}.conf"

  if [ -f "$conf_file" ]; then
    sed -i -E "s#root .*;#root ${new_docroot};#g" "$conf_file"
    sed -i -E "s#fastcgi_pass unix:.*;#fastcgi_pass unix:${socket};#g" "$conf_file"
  fi

  if [ -d "${SITE_DOMAINS_DIR}/${site}" ] && ls "${SITE_DOMAINS_DIR}/${site}"/*.conf >/dev/null 2>&1; then
    while IFS= read -r file; do
      domain="$(basename "$file" .conf)"
      behavior="$(grep -E '^BEHAVIOR=' "$file" | head -n1 | cut -d= -f2- | sed -E 's/^"//; s/"$//')"
      is_primary="$(grep -E '^IS_PRIMARY=' "$file" | head -n1 | cut -d= -f2- | sed -E 's/^"//; s/"$//')"
      site_domain_render_nginx "$site" "$domain" "$behavior" "$is_primary" >/dev/null
    done < <(ls -1 "${SITE_DOMAINS_DIR}/${site}"/*.conf)
  fi

  ensure_nginx_ok_and_reload

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"site transferred","site":"%s","old_user":"%s","new_user":"%s","new_user_id":"%s","docroot":"%s","move_files":"%s","update_domains":"%s"}\n' \
      "$(json_escape "$site")" \
      "$(json_escape "$old_user")" \
      "$(json_escape "$new_user")" \
      "$(json_escape "$new_user_id")" \
      "$(json_escape "$new_docroot")" \
      "$(json_escape "$move_files")" \
      "$(json_escape "$update_domains")"
  else
    echo "Site transferred: $site"
    echo "Old user: $old_user"
    echo "New user: $new_user"
    echo "Docroot: $new_docroot"
  fi
}

site_domain_primary() {
  require_root

  local site domain redirect_preview file preview_file preview_domain behavior type conf_file

  site="$(normalize_domain "$(get_arg "--site" "" "${ARGS[@]}")")"
  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"
  redirect_preview="$(get_arg "--redirect-preview" "1" "${ARGS[@]}")"

  file="$(site_domain_file_path "$site" "$domain")"

  if [ ! -f "$file" ]; then
    out_error "domain is not attached to site: $domain"
  fi

  site_domain_clear_primary "$site"
  sed -i -E 's/^IS_PRIMARY=.*/IS_PRIMARY="1"/' "$file"

  behavior="$(grep -E '^BEHAVIOR=' "$file" | head -n1 | cut -d= -f2- | sed -E 's/^"//; s/"$//')"

  if [ "$behavior" != "serve" ]; then
    sed -i -E 's/^BEHAVIOR=.*/BEHAVIOR="serve"/' "$file"
    behavior="serve"
  fi

  conf_file="$(site_domain_render_nginx "$site" "$domain" "serve" "1")"

  case "$redirect_preview" in
    1|true|yes)
      if [ -d "${SITE_DOMAINS_DIR}/${site}" ] && ls "${SITE_DOMAINS_DIR}/${site}"/*.conf >/dev/null 2>&1; then
        while IFS= read -r preview_file; do
          preview_domain="$(basename "$preview_file" .conf)"
          type="$(grep -E '^TYPE=' "$preview_file" | head -n1 | cut -d= -f2- | sed -E 's/^"//; s/"$//')"

          if [ "$preview_domain" != "$domain" ] && [ "$type" = "preview" ]; then
            sed -i -E 's/^BEHAVIOR=.*/BEHAVIOR="redirect_to_primary"/' "$preview_file"
            site_domain_render_nginx "$site" "$preview_domain" "redirect_to_primary" "0" >/dev/null
          fi
        done < <(ls -1 "${SITE_DOMAINS_DIR}/${site}"/*.conf)
      fi
    ;;
    0|false|no)
      true
    ;;
    *)
      out_error "invalid --redirect-preview. Use 1 or 0"
    ;;
  esac

  ensure_nginx_ok_and_reload

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"primary domain changed","site":"%s","domain":"%s","redirect_preview":"%s","nginx_config":"%s"}\n' \
      "$(json_escape "$site")" \
      "$(json_escape "$domain")" \
      "$(json_escape "$redirect_preview")" \
      "$(json_escape "$conf_file")"
  else
    echo "Primary domain changed"
    echo "Site: $site"
    echo "Primary: $domain"
  fi
}

site_list() {
  mkdir -p "$SITES_DIR"

  local files=()

  if ls "$SITES_DIR"/*.conf >/dev/null 2>&1; then
    while IFS= read -r file; do
      files+=("$file")
    done < <(ls -1 "$SITES_DIR"/*.conf)
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"sites":['

    local first=1
    local file domain

    for file in "${files[@]}"; do
      domain="$(basename "$file" .conf)"

      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '"%s"' "$(json_escape "$domain")"
      first=0
    done

    printf ']}\n'
  else
    if [ "${#files[@]}" -eq 0 ]; then
      echo "No sites found"
      return 0
    fi

    local file

    for file in "${files[@]}"; do
      basename "$file" .conf
    done
  fi
}

site_create() {
  require_root

  local domain user php_version root_suffix docroot socket conf_file site_file
  local server_alias www_domain
  local create_db db_type db_name db_user db_password db_created

  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"
  user="$(normalize_user "$(get_arg "--user" "$DEFAULT_USER" "${ARGS[@]}")")"
  php_version="$(get_arg "--php" "8.5" "${ARGS[@]}")"
  root_suffix="$(get_arg "--root" "public" "${ARGS[@]}")"

  create_db="$(get_arg "--db" "0" "${ARGS[@]}")"
  db_type="$(get_arg "--db-type" "" "${ARGS[@]}")"
  db_name="$(get_arg "--db-name" "" "${ARGS[@]}")"
  db_user="$(get_arg "--db-user" "" "${ARGS[@]}")"
  db_password="$(get_arg "--db-password" "" "${ARGS[@]}")"
  db_created="0"

  if ! echo "$php_version" | grep -Eq '^[0-9]+\.[0-9]+$'; then
    out_error "invalid PHP version: $php_version"
  fi

  socket="/run/sone-php/php${php_version}-fpm.sock"

  if [ ! -S "$socket" ]; then
    out_error "PHP-FPM socket not found: $socket"
  fi

  case "$create_db" in
    1|true|yes)
      create_db="1"
    ;;
    0|false|no)
      create_db="0"
    ;;
    *)
      out_error "invalid --db value. Use 1 or 0"
    ;;
  esac

  if [ "$create_db" = "1" ]; then
    if [ -n "$db_type" ]; then
      ARGS+=("--type=${db_type}")
    fi

    db_type="$(db_get_type)"
    db_name="$(db_normalize_name "$db_name" "db-name")"
    db_user="$(db_normalize_name "$db_user" "db-user")"

    if [ -z "$db_password" ]; then
      out_error "--db-password is required when --db=1"
    fi

    db_require_client "$db_type"
  fi

  ensure_user_exists "$user" "/bin/bash"

  docroot="/home/${user}/htdocs/${domain}"

  if [ -n "$root_suffix" ]; then
    docroot="${docroot}/${root_suffix}"
  fi

  mkdir -p "$docroot"

  if [ ! -f "${docroot}/index.php" ]; then
    cat > "${docroot}/index.php" <<'PHP'
<?php

phpinfo();
PHP
  fi

  chown -R "${user}:${user}" "/home/${user}/htdocs"
  chmod 711 "/home/${user}"
  find "/home/${user}/htdocs" -type d -exec chmod 755 {} \;
  find "/home/${user}/htdocs" -type f -exec chmod 644 {} \;

  if [ "$create_db" = "1" ]; then
    case "$db_type" in
      mariadb|mysql)
        db_exec "$db_type" "CREATE DATABASE IF NOT EXISTS \`${db_name}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
        db_exec "$db_type" "CREATE USER IF NOT EXISTS '${db_user}'@'localhost' IDENTIFIED BY '$(db_sql_escape "$db_password")';"
        db_exec "$db_type" "ALTER USER '${db_user}'@'localhost' IDENTIFIED BY '$(db_sql_escape "$db_password")';"
        db_exec "$db_type" "GRANT ALL PRIVILEGES ON \`${db_name}\`.* TO '${db_user}'@'localhost';"
        db_exec "$db_type" "FLUSH PRIVILEGES;"
      ;;

      postgresql)
        if ! sudo -u postgres psql -At -c "SELECT 1 FROM pg_roles WHERE rolname='${db_user}'" | grep -q 1; then
          sudo -u postgres psql -v ON_ERROR_STOP=1 -c "CREATE USER ${db_user} WITH PASSWORD '$(db_sql_escape "$db_password")';"
        else
          sudo -u postgres psql -v ON_ERROR_STOP=1 -c "ALTER USER ${db_user} WITH PASSWORD '$(db_sql_escape "$db_password")';"
        fi

        if ! sudo -u postgres psql -At -c "SELECT 1 FROM pg_database WHERE datname='${db_name}'" | grep -q 1; then
          sudo -u postgres createdb "$db_name"
        fi

        sudo -u postgres psql -v ON_ERROR_STOP=1 -c "GRANT ALL PRIVILEGES ON DATABASE ${db_name} TO ${db_user};"
        sudo -u postgres psql -v ON_ERROR_STOP=1 -d "$db_name" -c "GRANT ALL ON SCHEMA public TO ${db_user};"
      ;;
    esac

    db_created="1"
  fi

  mkdir -p "$SITES_DIR"

  site_file="${SITES_DIR}/${domain}.conf"

  cat > "$site_file" <<EOF_SITE
DOMAIN="${domain}"
USER="${user}"
PHP_VERSION="${php_version}"
ROOT_SUFFIX="${root_suffix}"
DOCROOT="${docroot}"
DB_ENABLED="${create_db}"
DB_TYPE="${db_type}"
DB_NAME="${db_name}"
DB_USER="${db_user}"
EOF_SITE

  conf_file="${NGINX_SITES_DIR}/sone-${domain}.conf"

  www_domain="www.${domain}"
  server_alias="${domain} ${www_domain}"

  cat > "$conf_file" <<EOF_NGINX
server {
    listen 80;
    listen [::]:80;

    server_name ${server_alias};

    root ${docroot};
    index index.php index.html index.htm;

    access_log /var/log/nginx/${domain}.access.log;
    error_log /var/log/nginx/${domain}.error.log;

    location / {
        try_files \$uri \$uri/ /index.php?\$query_string;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_index index.php;
        fastcgi_pass unix:${socket};
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT \$document_root;
    }

    location ~ /\. {
        deny all;
    }
}
EOF_NGINX

  ensure_nginx_ok_and_reload

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"site created","domain":"%s","user":"%s","php":"%s","root":"%s","nginx_config":"%s","db_enabled":"%s","db_created":"%s","db_type":"%s","db_name":"%s","db_user":"%s"}\n' \
      "$(json_escape "$domain")" \
      "$(json_escape "$user")" \
      "$(json_escape "$php_version")" \
      "$(json_escape "$docroot")" \
      "$(json_escape "$conf_file")" \
      "$(json_escape "$create_db")" \
      "$(json_escape "$db_created")" \
      "$(json_escape "$db_type")" \
      "$(json_escape "$db_name")" \
      "$(json_escape "$db_user")"
  else
    echo "Site created: ${domain}"
    echo "User: ${user}"
    echo "Root: ${docroot}"
    echo "PHP: ${php_version}"
    echo "Nginx: ${conf_file}"

    if [ "$create_db" = "1" ]; then
      echo "Database type: ${db_type}"
      echo "Database: ${db_name}"
      echo "Database user: ${db_user}"
    fi
  fi
}

generate_local_ssl() {
  require_root
  require_command openssl

  local local_domain cert_file key_file openssl_config

  local_domain="$(normalize_local_domain "${1:-}")"

  mkdir -p "$SSL_LOCAL_DIR"

  cert_file="${SSL_LOCAL_DIR}/${local_domain}.crt"
  key_file="${SSL_LOCAL_DIR}/${local_domain}.key"
  openssl_config="${SSL_LOCAL_DIR}/${local_domain}.openssl.cnf"

  cat > "$openssl_config" <<EOF_SSL
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
x509_extensions = v3_req

[dn]
CN = ${local_domain}

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = ${local_domain}
EOF_SSL

  openssl req \
    -x509 \
    -nodes \
    -days 3650 \
    -newkey rsa:2048 \
    -keyout "$key_file" \
    -out "$cert_file" \
    -config "$openssl_config" >/dev/null 2>&1

  chmod 600 "$key_file"
  chmod 644 "$cert_file"

  echo "${cert_file}|${key_file}"
}

site_ssl_local() {
  require_root

  local domain local_domain result cert_file key_file

  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"
  local_domain="$(normalize_local_domain "$(get_arg "--local-domain" "" "${ARGS[@]}")")"

  result="$(generate_local_ssl "$local_domain")"
  cert_file="${result%%|*}"
  key_file="${result##*|}"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"local ssl created","domain":"%s","local_domain":"%s","cert":"%s","key":"%s"}\n' \
      "$(json_escape "$domain")" \
      "$(json_escape "$local_domain")" \
      "$(json_escape "$cert_file")" \
      "$(json_escape "$key_file")"
  else
    echo "Local SSL created"
    echo "Domain: ${domain}"
    echo "Local domain: ${local_domain}"
    echo "Cert: ${cert_file}"
    echo "Key: ${key_file}"
  fi
}

site_local_domain_add() {
  require_root

  local domain local_domain php_version docroot socket conf_file result cert_file key_file

  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"
  local_domain="$(normalize_local_domain "$(get_arg "--local-domain" "" "${ARGS[@]}")")"

  php_version="$(get_site_value "$domain" "PHP_VERSION")"
  docroot="$(get_site_value "$domain" "DOCROOT")"

  if [ -z "$php_version" ]; then
    out_error "PHP_VERSION not found for site: $domain"
  fi

  if [ -z "$docroot" ]; then
    out_error "DOCROOT not found for site: $domain"
  fi

  socket="/run/sone-php/php${php_version}-fpm.sock"

  if [ ! -S "$socket" ]; then
    out_error "PHP-FPM socket not found: $socket"
  fi

  result="$(generate_local_ssl "$local_domain")"
  cert_file="${result%%|*}"
  key_file="${result##*|}"

  conf_file="${NGINX_SITES_DIR}/sone-${domain}-local.conf"

  cat > "$conf_file" <<EOF_NGINX
server {
    listen 80;
    listen [::]:80;

    server_name ${local_domain};

    return 301 https://\$host\$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name ${local_domain};

    ssl_certificate ${cert_file};
    ssl_certificate_key ${key_file};

    root ${docroot};
    index index.php index.html index.htm;

    access_log /var/log/nginx/${local_domain}.access.log;
    error_log /var/log/nginx/${local_domain}.error.log;

    location / {
        try_files \$uri \$uri/ /index.php?\$query_string;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_index index.php;
        fastcgi_pass unix:${socket};
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT \$document_root;
    }

    location ~ /\. {
        deny all;
    }
}
EOF_NGINX

  ensure_nginx_ok_and_reload

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"local domain added","domain":"%s","local_domain":"%s","php":"%s","root":"%s","nginx_config":"%s","cert":"%s"}\n' \
      "$(json_escape "$domain")" \
      "$(json_escape "$local_domain")" \
      "$(json_escape "$php_version")" \
      "$(json_escape "$docroot")" \
      "$(json_escape "$conf_file")" \
      "$(json_escape "$cert_file")"
  else
    echo "Local domain added"
    echo "Domain: ${domain}"
    echo "Local domain: ${local_domain}"
    echo "Root: ${docroot}"
    echo "PHP: ${php_version}"
    echo "Nginx: ${conf_file}"
    echo "Cert: ${cert_file}"
  fi
}

site_delete() {
  require_root

  local domain site_file conf_file local_conf_file

  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"

  if ! has_flag "--force" "${ARGS[@]}"; then
    if [ "$JSON" -eq 1 ]; then
      out_error "--force is required"
    fi

    read -rp "Delete site ${domain}? Type yes: " confirm

    if [ "$confirm" != "yes" ]; then
      echo "Canceled"
      exit 0
    fi
  fi

  site_file="${SITES_DIR}/${domain}.conf"
  conf_file="${NGINX_SITES_DIR}/sone-${domain}.conf"
  local_conf_file="${NGINX_SITES_DIR}/sone-${domain}-local.conf"

  rm -f "$site_file" "$conf_file" "$local_conf_file"

  ensure_nginx_ok_and_reload

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"site deleted","domain":"%s"}\n' "$(json_escape "$domain")"
  else
    echo "Site deleted: ${domain}"
  fi
}

site_php_switch() {
  require_root

  local domain php_version socket conf_file site_file local_conf_file

  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"
  php_version="$(get_arg "--php" "" "${ARGS[@]}")"

  if [ -z "$php_version" ]; then
    out_error "--php is required"
  fi

  if ! echo "$php_version" | grep -Eq '^[0-9]+\.[0-9]+$'; then
    out_error "invalid PHP version: $php_version"
  fi

  socket="/run/sone-php/php${php_version}-fpm.sock"
  conf_file="${NGINX_SITES_DIR}/sone-${domain}.conf"
  local_conf_file="${NGINX_SITES_DIR}/sone-${domain}-local.conf"
  site_file="${SITES_DIR}/${domain}.conf"

  if [ ! -S "$socket" ]; then
    out_error "PHP-FPM socket not found: $socket"
  fi

  if [ ! -f "$conf_file" ] && [ ! -f "$local_conf_file" ]; then
    out_error "nginx config not found for domain: $domain"
  fi

  if [ -f "$conf_file" ]; then
    sed -i -E "s|fastcgi_pass unix:/run/sone-php/php[0-9]+\.[0-9]+-fpm.sock;|fastcgi_pass unix:${socket};|" "$conf_file"
  fi

  if [ -f "$local_conf_file" ]; then
    sed -i -E "s|fastcgi_pass unix:/run/sone-php/php[0-9]+\.[0-9]+-fpm.sock;|fastcgi_pass unix:${socket};|" "$local_conf_file"
  fi

  if [ -f "$site_file" ]; then
    sed -i -E "s|^PHP_VERSION=.*|PHP_VERSION=\"${php_version}\"|" "$site_file"
  fi

  ensure_nginx_ok_and_reload

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"site PHP switched","domain":"%s","php":"%s"}\n' \
      "$(json_escape "$domain")" \
      "$(json_escape "$php_version")"
  else
    echo "Site ${domain} switched to PHP ${php_version}"
  fi
}

site_ssl_issue() {
  require_root

  local domain email include_www staging certbot_args=()

  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"
  email="$(get_arg "--email" "" "${ARGS[@]}")"
  include_www="$(get_arg "--www" "1" "${ARGS[@]}")"
  staging="$(get_arg "--staging" "0" "${ARGS[@]}")"

  if [ -z "$email" ]; then
    out_error "--email is required for Let's Encrypt"
  fi

  if ! command -v certbot >/dev/null 2>&1; then
    out_error "certbot not found. Install sone-acme"
  fi

  certbot_args=(
    --nginx
    -d "$domain"
    --non-interactive
    --agree-tos
    -m "$email"
  )

  case "$include_www" in
    1|true|yes)
      certbot_args+=(-d "www.${domain}")
    ;;
    0|false|no)
      true
    ;;
    *)
      out_error "invalid --www value. Use 1 or 0"
    ;;
  esac

  case "$staging" in
    1|true|yes)
      certbot_args+=(--staging)
    ;;
    0|false|no)
      true
    ;;
    *)
      out_error "invalid --staging value. Use 1 or 0"
    ;;
  esac

  certbot "${certbot_args[@]}"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"ssl issued","domain":"%s","www":"%s","staging":"%s","provider":"certbot"}\n' \
      "$(json_escape "$domain")" \
      "$(json_escape "$include_www")" \
      "$(json_escape "$staging")"
  else
    echo "SSL issued for: $domain"
    echo "WWW: $include_www"
    echo "Staging: $staging"
  fi
}

site_ssl_wildcard() {
  require_root

  local domain email

  domain="$(normalize_domain "$(get_arg "--domain" "" "${ARGS[@]}")")"
  email="$(get_arg "--email" "" "${ARGS[@]}")"

  if [ -z "$email" ]; then
    out_error "--email is required"
  fi

  require_command certbot

  if [ "$JSON" -eq 1 ]; then
    echo '{"ok":false,"error":"wildcard requires interactive DNS setup","code":1}'
    exit 1
  fi

  echo ""
  echo "==========================================="
  echo " WILDCARD SSL SETUP"
  echo "==========================================="
  echo ""
  echo "Domain: ${domain}"
  echo ""
  echo "IMPORTANT:"
  echo "Wildcard certificates require DNS verification."
  echo ""
  echo "You MUST create a TXT record:"
  echo ""
  echo "  _acme-challenge.${domain}"
  echo ""
  echo "Certbot will now show the required value."
  echo ""
  echo "==========================================="
  echo ""

  certbot certonly \
    --manual \
    --preferred-challenges dns \
    --agree-tos \
    --email "$email" \
    -d "*.${domain}" \
    -d "${domain}"

  echo ""
  echo "==========================================="
  echo " DONE"
  echo "==========================================="
}

db_normalize_name() {
  local value="${1:-}"
  local label="${2:-value}"

  if [ -z "$value" ]; then
    out_error "--${label} is required"
  fi

  if ! echo "$value" | grep -Eq '^[A-Za-z0-9_]+$'; then
    out_error "invalid ${label}: ${value}"
  fi

  echo "$value"
}

db_sql_escape() {
  local value="${1:-}"
  value="${value//\\/\\\\}"
  value="${value//\'/\'\'}"
  printf '%s' "$value"
}

db_get_type() {
  local type

  type="$(get_arg "--type" "" "${ARGS[@]}")"

  if [ -z "$type" ] && [ -f "$DB_PROVIDER_FILE" ]; then
    type="$(cat "$DB_PROVIDER_FILE" | tr -d '[:space:]')"
  fi

  if [ -z "$type" ]; then
    type="mariadb"
  fi

  case "$type" in
    mariadb|mysql|postgresql)
      echo "$type"
    ;;
    *)
      out_error "invalid database type: $type"
    ;;
  esac
}

db_require_client() {
  local type="${1:-}"

  if [ -z "$type" ]; then
    type="$(db_get_type)"
  fi

  case "$type" in
    mariadb)
      if command -v mariadb >/dev/null 2>&1; then
        return 0
      fi

      if command -v mysql >/dev/null 2>&1; then
        return 0
      fi

      out_error "mariadb/mysql client not found. Install sone-mariadb"
    ;;

    mysql)
      if command -v mysql >/dev/null 2>&1; then
        return 0
      fi

      if command -v mariadb >/dev/null 2>&1; then
        return 0
      fi

      out_error "mysql client not found. Install sone-mysql"
    ;;

    postgresql)
      if command -v psql >/dev/null 2>&1; then
        return 0
      fi

      out_error "psql client not found. Install sone-postgresql"
    ;;

    *)
      out_error "invalid database type: $type"
    ;;
  esac
}

db_mysql_client() {
  if command -v mariadb >/dev/null 2>&1; then
    echo "mariadb"
    return 0
  fi

  echo "mysql"
}

db_exec() {
  local type="${1:-}"
  local sql="${2:-}"
  local client

  if [ -z "$type" ]; then
    out_error "database type is required"
  fi

  if [ -z "$sql" ]; then
    out_error "sql query is required"
  fi

  db_require_client "$type"

  case "$type" in
    mariadb|mysql)
      client="$(db_mysql_client)"
      "$client" -uroot -e "$sql"
    ;;

    postgresql)
      sudo -u postgres psql -v ON_ERROR_STOP=1 -c "$sql"
    ;;

    *)
      out_error "invalid database type: $type"
    ;;
  esac
}

db_list() {
  require_root

  local type
  type="$(db_get_type)"
  db_require_client "$type"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"type":"%s","databases":[' "$(json_escape "$type")"

    local first=1
    local db
    local client

    case "$type" in
      mariadb|mysql)
        client="$(db_mysql_client)"

        while IFS= read -r db; do
          case "$db" in
            information_schema|mysql|performance_schema|sys)
              continue
            ;;
          esac

          if [ "$first" -eq 0 ]; then
            printf ','
          fi

          printf '"%s"' "$(json_escape "$db")"
          first=0
        done < <("$client" -uroot -N -e "SHOW DATABASES;")
      ;;

      postgresql)
        while IFS= read -r db; do
          case "$db" in
            postgres|template0|template1)
              continue
            ;;
          esac

          if [ "$first" -eq 0 ]; then
            printf ','
          fi

          printf '"%s"' "$(json_escape "$db")"
          first=0
        done < <(sudo -u postgres psql -At -c "SELECT datname FROM pg_database WHERE datistemplate = false;")
      ;;
    esac

    printf ']}\n'
  else
    case "$type" in
      mariadb|mysql)
        "$(db_mysql_client)" -uroot -e "SHOW DATABASES;"
      ;;

      postgresql)
        sudo -u postgres psql -c "\l"
      ;;
    esac
  fi
}

db_create() {
  require_root

  local type db charset collate

  type="$(db_get_type)"
  db="$(db_normalize_name "$(get_arg "--db" "" "${ARGS[@]}")" "db")"
  charset="$(get_arg "--charset" "utf8mb4" "${ARGS[@]}")"
  collate="$(get_arg "--collate" "utf8mb4_unicode_ci" "${ARGS[@]}")"

  case "$type" in
    mariadb|mysql)
      db_exec "$type" "CREATE DATABASE IF NOT EXISTS \`${db}\` CHARACTER SET ${charset} COLLATE ${collate};"
    ;;

    postgresql)
      if ! sudo -u postgres psql -At -c "SELECT 1 FROM pg_database WHERE datname='${db}'" | grep -q 1; then
        sudo -u postgres createdb "$db"
      fi
    ;;
  esac

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"database created","type":"%s","db":"%s"}\n' \
      "$(json_escape "$type")" \
      "$(json_escape "$db")"
  else
    echo "Database created: $db"
    echo "Type: $type"
  fi
}

db_delete() {
  require_root

  local type db

  type="$(db_get_type)"
  db="$(db_normalize_name "$(get_arg "--db" "" "${ARGS[@]}")" "db")"

  if ! has_flag "--force" "${ARGS[@]}"; then
    out_error "--force is required"
  fi

  case "$type" in
    mariadb|mysql)
      db_exec "$type" "DROP DATABASE IF EXISTS \`${db}\`;"
    ;;

    postgresql)
      sudo -u postgres dropdb --if-exists "$db"
    ;;
  esac

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"database deleted","type":"%s","db":"%s"}\n' \
      "$(json_escape "$type")" \
      "$(json_escape "$db")"
  else
    echo "Database deleted: $db"
    echo "Type: $type"
  fi
}

db_user_create() {
  require_root

  local type db user password host escaped_password

  type="$(db_get_type)"
  db="$(db_normalize_name "$(get_arg "--db" "" "${ARGS[@]}")" "db")"
  user="$(db_normalize_name "$(get_arg "--user" "" "${ARGS[@]}")" "user")"
  password="$(get_arg "--password" "" "${ARGS[@]}")"
  host="$(get_arg "--host" "localhost" "${ARGS[@]}")"

  if [ -z "$password" ]; then
    out_error "--password is required"
  fi

  if ! echo "$host" | grep -Eq '^[A-Za-z0-9_.%:-]+$'; then
    out_error "invalid host: $host"
  fi

  escaped_password="$(db_sql_escape "$password")"

  case "$type" in
    mariadb|mysql)
      db_exec "$type" "CREATE DATABASE IF NOT EXISTS \`${db}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
      db_exec "$type" "CREATE USER IF NOT EXISTS '${user}'@'${host}' IDENTIFIED BY '${escaped_password}';"
      db_exec "$type" "ALTER USER '${user}'@'${host}' IDENTIFIED BY '${escaped_password}';"
      db_exec "$type" "GRANT ALL PRIVILEGES ON \`${db}\`.* TO '${user}'@'${host}';"
      db_exec "$type" "FLUSH PRIVILEGES;"
    ;;

    postgresql)
      if ! sudo -u postgres psql -At -c "SELECT 1 FROM pg_roles WHERE rolname='${user}'" | grep -q 1; then
        sudo -u postgres psql -v ON_ERROR_STOP=1 -c "CREATE USER ${user} WITH PASSWORD '${escaped_password}';"
      else
        sudo -u postgres psql -v ON_ERROR_STOP=1 -c "ALTER USER ${user} WITH PASSWORD '${escaped_password}';"
      fi

      if ! sudo -u postgres psql -At -c "SELECT 1 FROM pg_database WHERE datname='${db}'" | grep -q 1; then
        sudo -u postgres createdb "$db"
      fi

      sudo -u postgres psql -v ON_ERROR_STOP=1 -c "GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${user};"
      sudo -u postgres psql -v ON_ERROR_STOP=1 -d "$db" -c "GRANT ALL ON SCHEMA public TO ${user};"
    ;;
  esac

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"database user created","type":"%s","db":"%s","user":"%s","host":"%s"}\n' \
      "$(json_escape "$type")" \
      "$(json_escape "$db")" \
      "$(json_escape "$user")" \
      "$(json_escape "$host")"
  else
    echo "Database user created: ${user}"
    echo "Database: ${db}"
    echo "Type: ${type}"
  fi
}

db_user_delete() {
  require_root

  local type user host

  type="$(db_get_type)"
  user="$(db_normalize_name "$(get_arg "--user" "" "${ARGS[@]}")" "user")"
  host="$(get_arg "--host" "localhost" "${ARGS[@]}")"

  if ! has_flag "--force" "${ARGS[@]}"; then
    out_error "--force is required"
  fi

  case "$type" in
    mariadb|mysql)
      if ! echo "$host" | grep -Eq '^[A-Za-z0-9_.%:-]+$'; then
        out_error "invalid host: $host"
      fi

      db_exec "$type" "DROP USER IF EXISTS '${user}'@'${host}';"
      db_exec "$type" "FLUSH PRIVILEGES;"
    ;;

    postgresql)
      sudo -u postgres psql -v ON_ERROR_STOP=1 -c "DROP USER IF EXISTS ${user};"
    ;;
  esac

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"database user deleted","type":"%s","user":"%s","host":"%s"}\n' \
      "$(json_escape "$type")" \
      "$(json_escape "$user")" \
      "$(json_escape "$host")"
  else
    echo "Database user deleted: ${user}"
    echo "Type: ${type}"
  fi
}

service_reload() {
  require_root

  local name

  name="$(get_arg "--name" "" "${ARGS[@]}")"

  if [ -z "$name" ]; then
    out_error "--name is required"
  fi

  case "$name" in
    nginx)
      nginx -t
      systemctl reload nginx
    ;;
    varnish)
      systemctl reload varnish
    ;;
    redis|redis-server)
      systemctl reload redis-server
    ;;
    php*|sone-php*)
      systemctl reload "$name"
    ;;
    *)
      out_error "service reload is not allowed for: $name"
    ;;
  esac

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"service reloaded","service":"%s"}\n' "$(json_escape "$name")"
  else
    echo "Service reloaded: $name"
  fi
}

cache_clear_nginx() {
  local cleared=0

  if [ -d /var/cache/nginx ]; then
    find /var/cache/nginx -mindepth 1 -delete
    cleared=1
  fi

  if [ "$cleared" -eq 1 ]; then
    echo "nginx cache cleared"
  else
    echo "nginx cache directory not found"
  fi
}

cache_clear_varnish() {
  if command -v varnishadm >/dev/null 2>&1; then
    varnishadm "ban req.url ~ ." >/dev/null
    echo "varnish cache ban created"
    return 0
  fi

  if systemctl list-unit-files | grep -q '^varnish.service'; then
    systemctl restart varnish
    echo "varnish restarted"
    return 0
  fi

  echo "varnish not found"
}

cache_clear_pagespeed() {
  local cleared=0

  if [ -d /var/cache/ngx_pagespeed ]; then
    find /var/cache/ngx_pagespeed -mindepth 1 -delete
    cleared=1
  fi

  if [ -d /var/cache/pagespeed ]; then
    find /var/cache/pagespeed -mindepth 1 -delete
    cleared=1
  fi

  if [ -d /var/ngx_pagespeed_cache ]; then
    find /var/ngx_pagespeed_cache -mindepth 1 -delete
    cleared=1
  fi

  if [ "$cleared" -eq 1 ]; then
    echo "pagespeed cache cleared"
  else
    echo "pagespeed cache directory not found"
  fi
}

cache_clear_redis() {
  require_command redis-cli

  if ! has_flag "--force" "${ARGS[@]}"; then
    out_error "redis cache clear requires --force"
  fi

  if has_flag "--all-databases" "${ARGS[@]}"; then
    redis-cli FLUSHALL >/dev/null
    echo "redis FLUSHALL executed"
  else
    redis-cli FLUSHDB >/dev/null
    echo "redis FLUSHDB executed"
  fi
}

cache_clear() {
  require_root

  local service output

  service="$(get_arg "--service" "" "${ARGS[@]}")"

  if [ -z "$service" ]; then
    out_error "--service is required"
  fi

  case "$service" in
    nginx)
      output="$(cache_clear_nginx)"
    ;;
    varnish)
      output="$(cache_clear_varnish)"
    ;;
    pagespeed)
      output="$(cache_clear_pagespeed)"
    ;;
    redis)
      output="$(cache_clear_redis)"
    ;;
    all)
      output=""
      output="${output}$(cache_clear_nginx)"$'\n'
      output="${output}$(cache_clear_varnish)"$'\n'
      output="${output}$(cache_clear_pagespeed)"$'\n'

      if has_flag "--force" "${ARGS[@]}"; then
        output="${output}$(cache_clear_redis)"$'\n'
      else
        output="${output}redis skipped, use --force"$'\n'
      fi
    ;;
    *)
      out_error "unknown cache service: $service"
    ;;
  esac

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"cache cleared","service":"%s","details":"%s"}\n' \
      "$(json_escape "$service")" \
      "$(json_escape "$output")"
  else
    printf '%s\n' "$output"
  fi
}

nginx_test() {
  require_command nginx

  nginx -t

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"nginx config is valid"}\n'
  fi
}

nginx_reload() {
  require_root

  nginx -t
  systemctl reload nginx

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"nginx reloaded"}\n'
  else
    echo "Nginx reloaded"
  fi
}

permissions_reset() {
  require_root

  local path dirs files

  path="$(get_arg "--path" "" "${ARGS[@]}")"
  dirs="$(get_arg "--directories" "755" "${ARGS[@]}")"
  files="$(get_arg "--files" "644" "${ARGS[@]}")"

  if [ -z "$path" ] || [ ! -e "$path" ]; then
    out_error "valid --path is required"
  fi

  find "$path" -type d -exec chmod "$dirs" {} \;
  find "$path" -type f -exec chmod "$files" {} \;

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"permissions reset","path":"%s","directories":"%s","files":"%s"}\n' \
      "$(json_escape "$path")" \
      "$(json_escape "$dirs")" \
      "$(json_escape "$files")"
  else
    echo "Permissions reset: $path"
  fi
}

package_version() {
  local package="${1:-}"

  if [ -z "$package" ]; then
    echo ""
    return 0
  fi

  dpkg-query -W -f='${Version}' "$package" 2>/dev/null || true
}

package_installed() {
  local package="${1:-}"

  dpkg-query -W -f='${Status}' "$package" 2>/dev/null | grep -q "install ok installed"
}

package_group() {
  local package="${1:-}"

  case "$package" in
    sone-php[0-9].*) echo "php" ;;
    sone-mysql|sone-mariadb|sone-postgresql) echo "database" ;;
    sone-redis) echo "cache" ;;
    sone-nginx) echo "webserver" ;;
	sone-logrotate|sone-backup|sone-mail|sone-cron|sone-monitoring|sone-limits|sone-queue|sone-ssl-renew) echo "system" ;;
    sone-openssh|sone-local-ssl|sone-fail2ban|sone-firewall|sone-antivirus) echo "security" ;;
    sone-phpmyadmin|sone-pgadmin) echo "database-tools" ;;
    sone-panel) echo "panel" ;;
    sone-cli) echo "core" ;;
    sone-hosting-layout) echo "layout" ;;
    *) echo "other" ;;
  esac
}

package_parent() {
  local package="${1:-}"

  case "$package" in
    sone-php[0-9].[0-9])
      echo ""
    ;;
    sone-php[0-9].[0-9]-*)
      echo "$package" | sed -E 's/^(sone-php[0-9]+\.[0-9]+)-.*/\1/'
    ;;
    sone-phpmyadmin)
      echo "sone-database-mysql-compatible"
    ;;
    sone-pgadmin)
      echo "sone-postgresql"
    ;;
    *)
      echo ""
    ;;
  esac
}

package_kind() {
  local package="${1:-}"

  case "$package" in
    sone-php[0-9].[0-9])
      echo "runtime"
    ;;
    sone-php[0-9].[0-9]-common)
      echo "runtime-part"
    ;;
    sone-php[0-9].[0-9]-cli|sone-php[0-9].[0-9]-fpm|sone-php[0-9].[0-9]-dev)
      echo "runtime-part"
    ;;
    sone-php[0-9].[0-9]-*)
      echo "extension"
    ;;
    sone-mysql|sone-mariadb|sone-postgresql|sone-redis|sone-nginx|sone-fail2ban|sone-firewall|sone-antivirus)
      echo "service"
    ;;
	sone-queue)
      echo "service"
    ;;
    sone-phpmyadmin|sone-pgadmin|sone-composer)
      echo "tool"
    ;;
	sone-backup)
      echo "service"
    ;;
    sone-cli)
      echo "core"
    ;;
	sone-logrotate)
      echo "config"
    ;;
	sone-monitoring)
      echo "config"
    ;;
	sone-ssl-renew)
      echo "scheduler"
    ;;
	sone-limits)
      echo "config"
    ;;
	sone-mail)
      echo "service"
    ;;
	sone-cron)
      echo "service"
    ;;
    sone-panel)
      echo "panel"
    ;;
    sone-hosting-layout)
      echo "layout"
    ;;
    *)
      echo "package"
    ;;
  esac
}

packages_list() {
  local packages

  packages="$(dpkg-query -W -f='${binary:Package}\n' 'sone-*' 2>/dev/null | sort -u || true)"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"packages":['

    local first=1
    local package sone_version upstream_package upstream_version
    local group kind parent

    while IFS= read -r package; do
      [ -z "$package" ] && continue

      sone_version="$(package_version "$package")"
      upstream_package="$(package_upstream_name "$package")"

      if echo "$upstream_package" | grep -q '^/'; then
        if [ -x "$upstream_package" ]; then
          upstream_version="$("$upstream_package" -r 2>/dev/null || true)"
        else
          upstream_version=""
        fi
      else
        upstream_version="$(package_version "$upstream_package")"
      fi

      group="$(package_group "$package")"
      kind="$(package_kind "$package")"
      parent="$(package_parent "$package")"

      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '{"name":"%s","installed":true,"group":"%s","kind":"%s","parent":"%s","sone_package":"%s","sone_version":"%s","upstream_package":"%s","upstream_version":"%s"}' \
        "$(json_escape "$package")" \
        "$(json_escape "$group")" \
        "$(json_escape "$kind")" \
        "$(json_escape "$parent")" \
        "$(json_escape "$package")" \
        "$(json_escape "$sone_version")" \
        "$(json_escape "$upstream_package")" \
        "$(json_escape "$upstream_version")"

      first=0
    done <<< "$packages"

    printf ']}\n'
  else
    printf '%-35s %-14s %-12s %-30s %-20s %-30s %-25s\n' \
      "NAME" "GROUP" "KIND" "PARENT" "SONE_VERSION" "UPSTREAM_PACKAGE" "UPSTREAM_VERSION"

    local package sone_version upstream_package upstream_version
    local group kind parent

    while IFS= read -r package; do
      [ -z "$package" ] && continue

      sone_version="$(package_version "$package")"
      upstream_package="$(package_upstream_name "$package")"

      if echo "$upstream_package" | grep -q '^/'; then
        if [ -x "$upstream_package" ]; then
          upstream_version="$("$upstream_package" -r 2>/dev/null || true)"
        else
          upstream_version=""
        fi
      else
        upstream_version="$(package_version "$upstream_package")"
      fi

      group="$(package_group "$package")"
      kind="$(package_kind "$package")"
      parent="$(package_parent "$package")"

      printf '%-35s %-14s %-12s %-30s %-20s %-30s %-25s\n' \
        "$package" "$group" "$kind" "$parent" "$sone_version" "$upstream_package" "$upstream_version"
    done <<< "$packages"
  fi
}

package_upstream_name() {
  local package="${1:-}"

  case "$package" in
    sone-cli)
      echo "sone-cli"
    ;;
    sone-hosting-layout)
      echo "sone-hosting-layout"
    ;;
    sone-nginx)
      echo "nginx"
    ;;
	sone-backup)
      echo "tar"
    ;;
	sone-mail)
      echo "postfix"
    ;;
	sone-cron)
      echo "cron"
    ;;
	sone-limits)
      echo "quota"
    ;;
	sone-monitoring)
      echo "procps"
    ;;
    sone-openssh)
      echo "openssh-server"
    ;;
	sone-ssl-renew)
      echo "certbot"
    ;;
	sone-fail2ban)
      echo "fail2ban"
    ;;
	sone-firewall)
      echo "ufw"
    ;;
	sone-antivirus)
      echo "clamav-daemon"
    ;;
	sone-logrotate)
      echo "logrotate"
    ;;
    sone-local-ssl)
      echo "openssl"
    ;;
	sone-queue)
      echo "supervisor"
    ;;
    sone-mariadb)
      echo "mariadb-server"
    ;;
    sone-mysql)
      echo "mysql-server"
    ;;
    sone-postgresql)
      echo "postgresql"
    ;;
    sone-redis)
      echo "redis-server"
    ;;
    sone-phpmyadmin)
      echo "sone-phpmyadmin"
    ;;
    sone-pgadmin)
      echo "pgadmin4-web"
    ;;
    sone-composer)
      echo "composer"
    ;;
    sone-panel)
      echo "sone-panel"
    ;;
    sone-php[0-9].[0-9]*)
      local php_version
      php_version="$(echo "$package" | sed -E 's/^sone-php([0-9]+\.[0-9]+).*/\1/')"
      echo "/opt/php/${php_version}/bin/php"
    ;;
    *)
      echo "$package"
    ;;
  esac
}

package_show() {
  local name sone_version upstream_package upstream_version installed
  local group kind parent

  name="$(get_arg "--name" "" "${ARGS[@]}")"

  if [ -z "$name" ]; then
    out_error "--name is required"
  fi

  if ! echo "$name" | grep -Eq '^sone-[A-Za-z0-9.+_-]+$'; then
    out_error "invalid package name: $name"
  fi

  installed="false"
  if package_installed "$name"; then
    installed="true"
  fi

  sone_version="$(package_version "$name")"
  upstream_package="$(package_upstream_name "$name")"

  if echo "$upstream_package" | grep -q '^/'; then
    if [ -x "$upstream_package" ]; then
      upstream_version="$("$upstream_package" -r 2>/dev/null || true)"
    else
      upstream_version=""
    fi
  else
    upstream_version="$(package_version "$upstream_package")"
  fi
  
  group="$(package_group "$name")"
  kind="$(package_kind "$name")"
  parent="$(package_parent "$name")"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"package":{"name":"%s","installed":%s,"group":"%s","kind":"%s","parent":"%s","sone_package":"%s","sone_version":"%s","upstream_package":"%s","upstream_version":"%s"}}\n' \
      "$(json_escape "$name")" \
      "$installed" \
      "$(json_escape "$group")" \
      "$(json_escape "$kind")" \
      "$(json_escape "$parent")" \
      "$(json_escape "$name")" \
      "$(json_escape "$sone_version")" \
      "$(json_escape "$upstream_package")" \
      "$(json_escape "$upstream_version")"
	  
  else
    echo "Name: $name"
    echo "Installed: $installed"
    echo "sOne version: $sone_version"
    echo "Upstream package: $upstream_package"
    echo "Upstream version: $upstream_version"
  fi
}


antivirus_env_load() {
  if [ -f /etc/sone/antivirus/clamav.env ]; then
    # shellcheck disable=SC1091
    . /etc/sone/antivirus/clamav.env
  fi
}

antivirus_installed() {
  command -v clamscan >/dev/null 2>&1 || package_installed "sone-antivirus"
}

antivirus_socket_path() {
  antivirus_env_load
  echo "${CLAMAV_LOCAL_SOCKET:-/run/clamav/clamd.ctl}"
}

antivirus_db_ready() {
  if ls /var/lib/clamav/daily.cvd /var/lib/clamav/daily.cld /var/lib/clamav/daily.inc >/dev/null 2>&1; then
    echo "true"
  else
    echo "false"
  fi
}

antivirus_service_active() {
  local service="${1:-}"

  systemctl is-active "$service" 2>/dev/null || echo "unknown"
}

antivirus_service_enabled() {
  local service="${1:-}"

  systemctl is-enabled "$service" 2>/dev/null || echo "unknown"
}

antivirus_status() {
  local installed package_installed_value socket socket_exists db_ready
  local freshclam_active freshclam_enabled daemon_active daemon_enabled version

  installed="false"
  package_installed_value="false"
  socket_exists="false"
  version=""

  if package_installed "sone-antivirus"; then
    package_installed_value="true"
  fi

  if antivirus_installed; then
    installed="true"
  fi

  socket="$(antivirus_socket_path)"

  if [ -S "$socket" ]; then
    socket_exists="true"
  fi

  db_ready="$(antivirus_db_ready)"
  freshclam_active="$(antivirus_service_active clamav-freshclam)"
  freshclam_enabled="$(antivirus_service_enabled clamav-freshclam)"
  daemon_active="$(antivirus_service_active clamav-daemon)"
  daemon_enabled="$(antivirus_service_enabled clamav-daemon)"

  if command -v clamscan >/dev/null 2>&1; then
    version="$(clamscan --version 2>/dev/null | head -n1 || true)"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"antivirus":{"package":"sone-antivirus","package_installed":%s,"installed":%s,"engine":"clamav","version":"%s","database_ready":%s,"socket":"%s","socket_exists":%s,"services":{"freshclam":{"active":"%s","enabled":"%s"},"daemon":{"active":"%s","enabled":"%s"}}}}\n' \
      "$package_installed_value" \
      "$installed" \
      "$(json_escape "$version")" \
      "$db_ready" \
      "$(json_escape "$socket")" \
      "$socket_exists" \
      "$(json_escape "$freshclam_active")" \
      "$(json_escape "$freshclam_enabled")" \
      "$(json_escape "$daemon_active")" \
      "$(json_escape "$daemon_enabled")"
  else
    echo "Package: sone-antivirus"
    echo "Package installed: $package_installed_value"
    echo "Installed: $installed"
    echo "Engine: clamav"
    echo "Version: ${version:-unknown}"
    echo "Database ready: $db_ready"
    echo "Socket: $socket"
    echo "Socket exists: $socket_exists"
    echo "freshclam: $freshclam_active / $freshclam_enabled"
    echo "daemon: $daemon_active / $daemon_enabled"
  fi
}

antivirus_socket() {
  local socket exists

  socket="$(antivirus_socket_path)"
  exists="false"

  if [ -S "$socket" ]; then
    exists="true"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"socket":"%s","exists":%s}\n' \
      "$(json_escape "$socket")" \
      "$exists"
  else
    echo "$socket"
  fi
}

antivirus_scan() {
  local scan_path socket scanner output exit_code ok infected

  scan_path="$(get_arg "--path" "" "${ARGS[@]}")"

  if [ -z "$scan_path" ]; then
    out_error "--path is required"
  fi

  if [ ! -e "$scan_path" ]; then
    out_error "scan path does not exist: $scan_path"
  fi

  socket="$(antivirus_socket_path)"
  scanner=""
  output=""
  exit_code=0
  ok="true"
  infected="false"

  set +e

  if command -v clamdscan >/dev/null 2>&1 && [ -S "$socket" ]; then
    scanner="clamdscan"
    output="$(clamdscan --fdpass "$scan_path" 2>&1)"
    exit_code="$?"
  elif command -v clamscan >/dev/null 2>&1; then
    scanner="clamscan"
    output="$(clamscan -r "$scan_path" 2>&1)"
    exit_code="$?"
  else
    set -e
    out_error "ClamAV scanner not found. Install sone-antivirus"
  fi

  set -e

  if [ "$exit_code" -eq 1 ]; then
    ok="false"
    infected="true"
  elif [ "$exit_code" -gt 1 ]; then
    ok="false"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":%s,"scanner":"%s","path":"%s","infected":%s,"exit_code":%s,"output":"%s"}\n' \
      "$ok" \
      "$(json_escape "$scanner")" \
      "$(json_escape "$scan_path")" \
      "$infected" \
      "$exit_code" \
      "$(json_escape "$output")"
  else
    printf '%s\n' "$output"
    return "$exit_code"
  fi
}

antivirus_update() {
  require_root

  if ! command -v freshclam >/dev/null 2>&1; then
    out_error "freshclam not found. Install sone-antivirus"
  fi

  systemctl restart clamav-freshclam >/dev/null 2>&1 || true

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"freshclam restart requested"}\n'
  else
    echo "freshclam restart requested"
  fi
}

firewall_installed() {
  command -v ufw >/dev/null 2>&1
}

firewall_status() {
  if ! firewall_installed; then
    if [ "$JSON" -eq 1 ]; then
      printf '{"ok":true,"installed":false,"enabled":false,"rules":[]}\n'
    else
      echo "UFW is not installed"
    fi
    return 0
  fi

  local status enabled
  status="$(ufw status verbose 2>/dev/null || true)"

  enabled=false
  if echo "$status" | grep -qi '^Status: active'; then
    enabled=true
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"installed":true,"enabled":%s,"raw":"%s"}\n' \
      "$enabled" \
      "$(json_escape "$status")"
  else
    echo "$status"
  fi
}

firewall_enable() {
  require_root

  if ! firewall_installed; then
    out_error "ufw is not installed"
  fi

  ufw --force enable

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"firewall enabled"}\n'
  else
    echo "Firewall enabled"
  fi
}

firewall_disable() {
  require_root

  if ! firewall_installed; then
    out_error "ufw is not installed"
  fi

  ufw --force disable

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"firewall disabled"}\n'
  else
    echo "Firewall disabled"
  fi
}

firewall_reload() {
  require_root

  if ! firewall_installed; then
    out_error "ufw is not installed"
  fi

  ufw reload

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"firewall reloaded"}\n'
  else
    echo "Firewall reloaded"
  fi
}

firewall_validate_port() {
  local port="${1:-}"

  if ! echo "$port" | grep -Eq '^[0-9]+$'; then
    out_error "invalid port: $port"
  fi

  if [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
    out_error "port must be between 1 and 65535"
  fi
}

firewall_validate_proto() {
  local proto="${1:-}"

  case "$proto" in
    tcp|udp)
      return 0
    ;;
    *)
      out_error "invalid proto: $proto"
    ;;
  esac
}

firewall_allow() {
  require_root

  local port proto

  port="$(get_arg "--port" "" "${ARGS[@]}")"
  proto="$(get_arg "--proto" "tcp" "${ARGS[@]}")"

  if [ -z "$port" ]; then
    out_error "--port is required"
  fi

  firewall_validate_port "$port"
  firewall_validate_proto "$proto"

  if ! firewall_installed; then
    out_error "ufw is not installed"
  fi

  if [ "$JSON" -eq 1 ]; then
    ufw allow "${port}/${proto}" >/dev/null 2>&1
  else
    ufw allow "${port}/${proto}"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"firewall rule added","port":%s,"proto":"%s","action":"allow"}\n' \
      "$port" \
      "$(json_escape "$proto")"
  else
    echo "Firewall allow: ${port}/${proto}"
  fi
}

firewall_delete() {
  require_root

  local port proto

  port="$(get_arg "--port" "" "${ARGS[@]}")"
  proto="$(get_arg "--proto" "tcp" "${ARGS[@]}")"

  if [ -z "$port" ]; then
    out_error "--port is required"
  fi

  firewall_validate_port "$port"
  firewall_validate_proto "$proto"

  if ! firewall_installed; then
    out_error "ufw is not installed"
  fi

  if [ "$JSON" -eq 1 ]; then
    ufw delete allow "${port}/${proto}" >/dev/null 2>&1 || true
  else
    ufw delete allow "${port}/${proto}" || true
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"firewall rule deleted","port":%s,"proto":"%s","action":"delete"}\n' \
      "$port" \
      "$(json_escape "$proto")"
  else
    echo "Firewall delete: ${port}/${proto}"
  fi
}

fail2ban_installed() {
  command -v fail2ban-client >/dev/null 2>&1
}

fail2ban_running() {
  systemctl is-active --quiet fail2ban 2>/dev/null
}

fail2ban_status() {
  local installed running raw jails

  installed=false
  running=false
  raw=""
  jails=""

  if fail2ban_installed; then
    installed=true
  fi

  if fail2ban_running; then
    running=true
    raw="$(fail2ban-client status 2>/dev/null || true)"
    jails="$(echo "$raw" | sed -n 's/.*Jail list:[[:space:]]*//p' | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -v '^$' || true)"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"installed":%s,"running":%s,"jails":[' "$installed" "$running"

    local first=1 jail
    while IFS= read -r jail; do
      [ -z "$jail" ] && continue

      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '"%s"' "$(json_escape "$jail")"
      first=0
    done <<< "$jails"

    printf '],"raw":"%s"}\n' "$(json_escape "$raw")"
  else
    if [ "$installed" != "true" ]; then
      echo "Fail2Ban is not installed"
      return 0
    fi

    if [ "$running" != "true" ]; then
      echo "Fail2Ban is installed but not running"
      return 0
    fi

    echo "$raw"
  fi
}

fail2ban_jails() {
  fail2ban_status
}

fail2ban_jail() {
  local name raw banned

  name="$(get_arg "--name" "" "${ARGS[@]}")"

  if [ -z "$name" ]; then
    out_error "--name is required"
  fi

  if ! echo "$name" | grep -Eq '^[A-Za-z0-9._-]+$'; then
    out_error "invalid jail name: $name"
  fi

  if ! fail2ban_installed; then
    out_error "fail2ban-client is not installed"
  fi

  raw="$(fail2ban-client status "$name" 2>/dev/null || true)"

  if [ -z "$raw" ]; then
    out_error "jail not found or not active: $name"
  fi

  banned="$(echo "$raw" | sed -n 's/.*Banned IP list:[[:space:]]*//p' || true)"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"jail":"%s","banned_raw":"%s","raw":"%s"}\n' \
      "$(json_escape "$name")" \
      "$(json_escape "$banned")" \
      "$(json_escape "$raw")"
  else
    echo "$raw"
  fi
}

fail2ban_ban() {
  require_root

  local jail ip

  jail="$(get_arg "--jail" "" "${ARGS[@]}")"
  ip="$(get_arg "--ip" "" "${ARGS[@]}")"

  if [ -z "$jail" ]; then
    out_error "--jail is required"
  fi

  if [ -z "$ip" ]; then
    out_error "--ip is required"
  fi

  if ! echo "$jail" | grep -Eq '^[A-Za-z0-9._-]+$'; then
    out_error "invalid jail name: $jail"
  fi

  if ! fail2ban_installed; then
    out_error "fail2ban-client is not installed"
  fi

  if [ "$JSON" -eq 1 ]; then
    fail2ban-client set "$jail" banip "$ip" >/dev/null 2>&1
  else
    fail2ban-client set "$jail" banip "$ip"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"ip banned","jail":"%s","ip":"%s"}\n' \
      "$(json_escape "$jail")" \
      "$(json_escape "$ip")"
  else
    echo "Banned $ip in jail $jail"
  fi
}

fail2ban_unban() {
  require_root

  local jail ip

  jail="$(get_arg "--jail" "" "${ARGS[@]}")"
  ip="$(get_arg "--ip" "" "${ARGS[@]}")"

  if [ -z "$jail" ]; then
    out_error "--jail is required"
  fi

  if [ -z "$ip" ]; then
    out_error "--ip is required"
  fi

  if ! echo "$jail" | grep -Eq '^[A-Za-z0-9._-]+$'; then
    out_error "invalid jail name: $jail"
  fi

  if ! fail2ban_installed; then
    out_error "fail2ban-client is not installed"
  fi

  if [ "$JSON" -eq 1 ]; then
    fail2ban-client set "$jail" unbanip "$ip" >/dev/null 2>&1 || true
  else
    fail2ban-client set "$jail" unbanip "$ip" || true
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"ip unbanned","jail":"%s","ip":"%s"}\n' \
      "$(json_escape "$jail")" \
      "$(json_escape "$ip")"
  else
    echo "Unbanned $ip from jail $jail"
  fi
}

logs_base_dir_for_key() {
  local key="$1"

  case "$key" in
    sone/*)
      echo "/var/log/sone"
    ;;
    nginx/*)
      echo "/var/log/nginx"
    ;;
    redis/*)
      echo "/var/log/redis"
    ;;
    phpmyadmin/*)
      echo "/var/log/phpmyadmin"
    ;;
    pgadmin/*)
      echo "/var/log/pgadmin"
    ;;
    php/*/*)
      echo "/opt/php"
    ;;
    *)
      echo ""
    ;;
  esac
}

logs_resolve_path() {
  local key="$1"
  local base path php_version rest

  if [ -z "$key" ]; then
    out_error "--file is required"
  fi

  if echo "$key" | grep -Eq '(^/|(^|/)\.\.(/|$))'; then
    out_error "invalid log file path"
  fi

  base="$(logs_base_dir_for_key "$key")"

  if [ -z "$base" ]; then
    out_error "log file is not allowed: $key"
  fi

  case "$key" in
    sone/*)
      path="/var/log/${key}"
    ;;
    nginx/*)
      path="/var/log/${key}"
    ;;
    redis/*)
      path="/var/log/${key}"
    ;;
    phpmyadmin/*)
      path="/var/log/${key}"
    ;;
    pgadmin/*)
      path="/var/log/${key}"
    ;;
    php/*/*)
      php_version="$(echo "$key" | cut -d/ -f2)"
      rest="$(echo "$key" | cut -d/ -f3-)"

      if ! echo "$php_version" | grep -Eq '^[0-9]+\.[0-9]+$'; then
        out_error "invalid php version in log key"
      fi

      path="/opt/php/${php_version}/var/log/${rest}"
    ;;
  esac

  if [ ! -f "$path" ]; then
    out_error "log file not found: $key"
  fi

  echo "$path"
}

logs_validate_lines() {
  local lines="$1"

  if ! echo "$lines" | grep -Eq '^[0-9]+$'; then
    out_error "invalid --lines value"
  fi

  if [ "$lines" -lt 1 ]; then
    out_error "--lines must be greater than 0"
  fi

  if [ "$lines" -gt 2000 ]; then
    out_error "--lines must be less than or equal to 2000"
  fi
}

logs_list() {
  local files file key size modified first

  files="$(
    find /var/log/sone /var/log/nginx /var/log/redis /var/log/phpmyadmin /var/log/pgadmin \
      -maxdepth 1 -type f -name '*.log' 2>/dev/null || true

    find /opt/php -path '/opt/php/*/var/log/*.log' -type f 2>/dev/null || true
  )"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"logs":['

    first=1

    while IFS= read -r file; do
      [ -z "$file" ] && continue

      case "$file" in
        /var/log/sone/*)
          key="sone/$(basename "$file")"
        ;;
        /var/log/nginx/*)
          key="nginx/$(basename "$file")"
        ;;
        /var/log/redis/*)
          key="redis/$(basename "$file")"
        ;;
        /var/log/phpmyadmin/*)
          key="phpmyadmin/$(basename "$file")"
        ;;
        /var/log/pgadmin/*)
          key="pgadmin/$(basename "$file")"
        ;;
        /opt/php/*/var/log/*)
          key="php/$(echo "$file" | sed -E 's#^/opt/php/([^/]+)/var/log/(.*)$#\1/\2#')"
        ;;
        *)
          continue
        ;;
      esac

      size="$(stat -c '%s' "$file" 2>/dev/null || echo 0)"
      modified="$(stat -c '%y' "$file" 2>/dev/null | cut -d'.' -f1 || true)"

      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '{"key":"%s","path":"%s","size":%s,"modified":"%s"}' \
        "$(json_escape "$key")" \
        "$(json_escape "$file")" \
        "$size" \
        "$(json_escape "$modified")"

      first=0
    done <<< "$files"

    printf ']}\n'
  else
    echo "$files"
  fi
}

logs_read() {
  local key lines path content

  key="$(get_arg "--file" "" "${ARGS[@]}")"
  lines="$(get_arg "--lines" "200" "${ARGS[@]}")"

  logs_validate_lines "$lines"
  path="$(logs_resolve_path "$key")"

  content="$(tail -n "$lines" "$path" 2>/dev/null || true)"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"file":"%s","path":"%s","lines":%s,"content":"%s"}\n' \
      "$(json_escape "$key")" \
      "$(json_escape "$path")" \
      "$lines" \
      "$(json_escape "$content")"
  else
    echo "$content"
  fi
}

logs_tail() {
  logs_read
}

backup_validate_user() {
  local user="$1"

  if ! echo "$user" | grep -Eq '^[a-zA-Z0-9._-]+$'; then
    out_error "invalid user"
  fi

  if ! id "$user" >/dev/null 2>&1; then
    out_error "user not found: $user"
  fi
}

backup_root() {
  local user="$1"

  echo "/home/${user}/backups"
}

backup_files_dir() {
  local user="$1"

  echo "$(backup_root "$user")/files"
}

backup_databases_dir() {
  local user="$1"

  echo "$(backup_root "$user")/databases"
}

backup_ensure_dirs() {
  local user="$1"

  mkdir -p "$(backup_files_dir "$user")"
  mkdir -p "$(backup_databases_dir "$user")"

  chown -R "$user:$user" "$(backup_root "$user")" || true
}

backup_validate_rel_file() {
  local file="$1"

  if [ -z "$file" ]; then
    out_error "--file is required"
  fi

  if echo "$file" | grep -Eq '(^/|(^|/)\.\.(/|$))'; then
    out_error "invalid backup file"
  fi
}

backup_create() {
  require_root

  local user type domain database file ts output target

  user="$(get_arg "--user" "" "${ARGS[@]}")"
  type="$(get_arg "--type" "" "${ARGS[@]}")"
  domain="$(get_arg "--domain" "" "${ARGS[@]}")"
  database="$(get_arg "--database" "" "${ARGS[@]}")"
  file="$(get_arg "--file" "" "${ARGS[@]}")"

  backup_validate_user "$user"
  backup_ensure_dirs "$user"

  ts="$(date +%Y%m%d-%H%M%S)"

  case "$type" in
    files)
      if [ -z "$domain" ]; then
        out_error "--domain is required"
      fi

      target="/home/${user}/htdocs/${domain}"

      if [ ! -d "$target" ]; then
        out_error "site directory not found: $target"
      fi

      output="$(backup_files_dir "$user")/${domain}-${ts}.tar.gz"

      if ! tar -czf "$output" -C "/home/${user}/htdocs" "${domain}" 2>/tmp/sone-backup-error.log; then
        out_error "$(cat /tmp/sone-backup-error.log)"
      fi
    ;;

    mysql)
      if [ -z "$database" ]; then
        out_error "--database is required"
      fi

      output="$(backup_databases_dir "$user")/${database}-${ts}.sql.gz"

      if ! mysqldump "$database" 2>/tmp/sone-backup-error.log | gzip > "$output"; then
        out_error "$(cat /tmp/sone-backup-error.log)"
      fi
    ;;

    postgresql)
      if [ -z "$database" ]; then
        out_error "--database is required"
      fi

      output="$(backup_databases_dir "$user")/${database}-${ts}.sql.gz"

      if ! sudo -u postgres pg_dump "$database" 2>/tmp/sone-backup-error.log | gzip > "$output"; then
        out_error "$(cat /tmp/sone-backup-error.log)"
      fi
    ;;

    sqlite)
      if [ -z "$file" ]; then
        out_error "--file is required"
      fi

      if [ ! -f "$file" ]; then
        out_error "sqlite file not found"
      fi

      output="$(backup_databases_dir "$user")/$(basename "$file")-${ts}.gz"

      if ! gzip -c "$file" > "$output" 2>/tmp/sone-backup-error.log; then
        out_error "$(cat /tmp/sone-backup-error.log)"
      fi
    ;;

    *)
      out_error "invalid backup type"
    ;;
  esac

  chown "$user:$user" "$output" || true

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"type":"%s","file":"%s"}\n' \
      "$(json_escape "$type")" \
      "$(json_escape "$output")"
  else
    echo "$output"
  fi
}

backup_list() {
  local user root files first file size modified rel

  user="$(get_arg "--user" "" "${ARGS[@]}")"

  backup_validate_user "$user"

  root="$(backup_root "$user")"

  files="$(find "$root" -type f 2>/dev/null | sort || true)"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"files":['

    first=1

    while IFS= read -r file; do
      [ -z "$file" ] && continue

      rel="${file#$root/}"

      size="$(stat -c '%s' "$file" 2>/dev/null || echo 0)"
      modified="$(stat -c '%y' "$file" 2>/dev/null | cut -d'.' -f1 || true)"

      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '{"file":"%s","size":%s,"modified":"%s"}' \
        "$(json_escape "$rel")" \
        "$size" \
        "$(json_escape "$modified")"

      first=0
    done <<< "$files"

    printf ']}\n'
  else
    echo "$files"
  fi
}

backup_delete() {
  require_root

  local user file path

  user="$(get_arg "--user" "" "${ARGS[@]}")"
  file="$(get_arg "--file" "" "${ARGS[@]}")"

  backup_validate_user "$user"
  backup_validate_rel_file "$file"

  path="$(backup_root "$user")/${file}"

  if [ ! -f "$path" ]; then
    out_error "backup file not found"
  fi

  rm -f "$path"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"deleted":"%s"}\n' "$(json_escape "$file")"
  else
    echo "Deleted: $file"
  fi
}

backup_restore() {
  require_root

  local user type domain database file target path

  user="$(get_arg "--user" "" "${ARGS[@]}")"
  type="$(get_arg "--type" "" "${ARGS[@]}")"
  domain="$(get_arg "--domain" "" "${ARGS[@]}")"
  database="$(get_arg "--database" "" "${ARGS[@]}")"
  file="$(get_arg "--file" "" "${ARGS[@]}")"
  target="$(get_arg "--target" "" "${ARGS[@]}")"

  backup_validate_user "$user"
  backup_validate_rel_file "$file"

  path="$(backup_root "$user")/${file}"

  if [ ! -f "$path" ]; then
    out_error "backup file not found"
  fi

  case "$type" in
    files)
      if [ -z "$domain" ]; then
        out_error "--domain is required"
      fi

      rm -rf "/home/${user}/htdocs/${domain}"

      mkdir -p "/home/${user}/htdocs"

      if ! tar -xzf "$path" -C "/home/${user}/htdocs" 2>/tmp/sone-backup-error.log; then
        out_error "$(cat /tmp/sone-backup-error.log)"
      fi

      chown -R "$user:$user" "/home/${user}/htdocs/${domain}" || true
    ;;

    mysql)
      if [ -z "$database" ]; then
        out_error "--database is required"
      fi

      if ! gunzip -c "$path" | mysql "$database" 2>/tmp/sone-backup-error.log; then
        out_error "$(cat /tmp/sone-backup-error.log)"
      fi
    ;;

    postgresql)
      if [ -z "$database" ]; then
        out_error "--database is required"
      fi

      if ! gunzip -c "$path" | sudo -u postgres psql "$database" 2>/tmp/sone-backup-error.log; then
        out_error "$(cat /tmp/sone-backup-error.log)"
      fi
    ;;

    sqlite)
      if [ -z "$target" ]; then
        out_error "--target is required"
      fi

      if ! gunzip -c "$path" > "$target" 2>/tmp/sone-backup-error.log; then
        out_error "$(cat /tmp/sone-backup-error.log)"
      fi

      chown "$user:$user" "$target" || true
    ;;

    *)
      out_error "invalid restore type"
    ;;
  esac

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"restored":"%s","type":"%s"}\n' \
      "$(json_escape "$file")" \
      "$(json_escape "$type")"
  else
    echo "Restored: $file"
  fi
}

mail_config_file() {
  echo "/etc/sone/mail/relay.conf"
}

mail_installed() {
  command -v postfix >/dev/null 2>&1 || command -v postconf >/dev/null 2>&1
}

mail_running() {
  systemctl is-active --quiet postfix 2>/dev/null
}

mail_status() {
  local installed running relay_enabled relay_host relay_port relay_security queue_count mailname

  installed=false
  running=false
  relay_enabled=0
  relay_host=""
  relay_port=""
  relay_security=""
  queue_count=0
  mailname=""

  if mail_installed; then
    installed=true
  fi

  if mail_running; then
    running=true
  fi

  if [ -f /etc/mailname ]; then
    mailname="$(cat /etc/mailname 2>/dev/null || true)"
  fi

  if [ -f "$(mail_config_file)" ]; then
    . "$(mail_config_file)"
    relay_enabled="${SMTP_RELAY_ENABLED:-0}"
    relay_host="${SMTP_RELAY_HOST:-}"
    relay_port="${SMTP_RELAY_PORT:-}"
    relay_security="${SMTP_RELAY_SECURITY:-}"
  fi

  if command -v mailq >/dev/null 2>&1; then
    queue_count="$(mailq 2>/dev/null | grep -c '^[A-F0-9]' || true)"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"installed":%s,"running":%s,"mailname":"%s","relay":{"enabled":%s,"host":"%s","port":"%s","security":"%s"},"queue_count":%s}\n' \
      "$installed" \
      "$running" \
      "$(json_escape "$mailname")" \
      "$([ "$relay_enabled" = "1" ] && echo true || echo false)" \
      "$(json_escape "$relay_host")" \
      "$(json_escape "$relay_port")" \
      "$(json_escape "$relay_security")" \
      "$queue_count"
  else
    echo "Installed: $installed"
    echo "Running: $running"
    echo "Mailname: $mailname"
    echo "Relay enabled: $relay_enabled"
    echo "Relay host: $relay_host"
    echo "Queue count: $queue_count"
  fi
}

mail_queue() {
  local raw

  if ! command -v mailq >/dev/null 2>&1; then
    out_error "mailq is not installed"
  fi

  raw="$(mailq 2>/dev/null || true)"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"raw":"%s"}\n' "$(json_escape "$raw")"
  else
    echo "$raw"
  fi
}

mail_test() {
  require_root

  local to from subject body tmpfile hostname_fqdn

  to="$(get_arg "--to" "" "${ARGS[@]}")"
  from="$(get_arg "--from" "" "${ARGS[@]}")"
  subject="$(get_arg "--subject" "sOne test mail" "${ARGS[@]}")"
  body="$(get_arg "--body" "" "${ARGS[@]}")"

  if [ -z "$to" ]; then
    out_error "--to is required"
  fi

  if ! echo "$to" | grep -Eq '^[^@[:space:]]+@[^@[:space:]]+\.[^@[:space:]]+$'; then
    out_error "invalid recipient email"
  fi

  if [ -n "$from" ]; then
    if ! echo "$from" | grep -Eq '^[^@[:space:]]+@[^@[:space:]]+\.[^@[:space:]]+$'; then
      out_error "invalid from email"
    fi
  fi

  if ! command -v sendmail >/dev/null 2>&1; then
    out_error "sendmail is not installed"
  fi

  hostname_fqdn="$(hostname -f 2>/dev/null || hostname)"

  if [ -z "$body" ]; then
    body="sOne test mail from ${hostname_fqdn}"
  fi

  tmpfile="$(mktemp)"

  {
    echo "To: ${to}"

    if [ -n "$from" ]; then
      echo "From: ${from}"
    fi

    echo "Subject: ${subject}"
    echo "Content-Type: text/plain; charset=UTF-8"
    echo
    echo "${body}"
  } > "$tmpfile"

  if [ -n "$from" ]; then
    if ! sendmail -f "$from" -t < "$tmpfile" >/tmp/sone-mail-error.log 2>&1; then
      rm -f "$tmpfile"
      out_error "$(cat /tmp/sone-mail-error.log)"
    fi
  else
    if ! sendmail -t < "$tmpfile" >/tmp/sone-mail-error.log 2>&1; then
      rm -f "$tmpfile"
      out_error "$(cat /tmp/sone-mail-error.log)"
    fi
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"test mail queued","to":"%s","from":"%s","subject":"%s"}\n' \
      "$(json_escape "$to")" \
      "$(json_escape "$from")" \
      "$(json_escape "$subject")"
  else
    echo "Test mail queued"
  fi
}

mail_apply_relay() {
  local config relay_enabled relay_host relay_port relay_username relay_password relay_security

  config="$(mail_config_file)"

  if [ ! -f "$config" ]; then
    out_error "mail relay config not found"
  fi

  . "$config"

  relay_enabled="${SMTP_RELAY_ENABLED:-0}"
  relay_host="${SMTP_RELAY_HOST:-}"
  relay_port="${SMTP_RELAY_PORT:-587}"
  relay_username="${SMTP_RELAY_USERNAME:-}"
  relay_password="${SMTP_RELAY_PASSWORD:-}"
  relay_security="${SMTP_RELAY_SECURITY:-tls}"

  postconf -e "inet_interfaces = loopback-only"
  postconf -e "inet_protocols = all"
  postconf -e "mydestination = localhost"
  postconf -e "mynetworks = 127.0.0.0/8 [::1]/128"

  if [ "$relay_enabled" = "1" ] && [ -n "$relay_host" ]; then
    postconf -e "relayhost = [${relay_host}]:${relay_port}"
    postconf -e "smtp_sasl_auth_enable = yes"
    postconf -e "smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd"
    postconf -e "smtp_sasl_security_options = noanonymous"

    if [ "$relay_security" = "tls" ]; then
      postconf -e "smtp_tls_security_level = encrypt"
    else
      postconf -e "smtp_tls_security_level = may"
    fi

    postconf -e "smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt"

    cat > /etc/postfix/sasl_passwd <<EOF
[${relay_host}]:${relay_port} ${relay_username}:${relay_password}
EOF

    chmod 0600 /etc/postfix/sasl_passwd
    postmap /etc/postfix/sasl_passwd
  else
    postconf -e "relayhost ="
    postconf -e "smtp_sasl_auth_enable = no"
    rm -f /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
  fi

  systemctl restart postfix >/dev/null 2>&1 || true
}

mail_relay_set() {
  require_root

  local host port username password security config

  host="$(get_arg "--host" "" "${ARGS[@]}")"
  port="$(get_arg "--port" "587" "${ARGS[@]}")"
  username="$(get_arg "--username" "" "${ARGS[@]}")"
  password="$(get_arg "--password" "" "${ARGS[@]}")"
  security="$(get_arg "--security" "tls" "${ARGS[@]}")"
  config="$(mail_config_file)"

  if [ -z "$host" ]; then
    out_error "--host is required"
  fi

  if ! echo "$port" | grep -Eq '^[0-9]+$'; then
    out_error "invalid port"
  fi

  case "$security" in
    tls|none)
    ;;
    *)
      out_error "invalid security"
    ;;
  esac

  mkdir -p "$(dirname "$config")"

  cat > "$config" <<EOF
SMTP_RELAY_ENABLED=1
SMTP_RELAY_HOST=${host}
SMTP_RELAY_PORT=${port}
SMTP_RELAY_USERNAME=${username}
SMTP_RELAY_PASSWORD=${password}
SMTP_RELAY_SECURITY=${security}
EOF

  chmod 0600 "$config"

  mail_apply_relay

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"mail relay configured","host":"%s","port":%s,"security":"%s"}\n' \
      "$(json_escape "$host")" \
      "$port" \
      "$(json_escape "$security")"
  else
    echo "Mail relay configured: $host:$port"
  fi
}

mail_relay_disable() {
  require_root

  local config

  config="$(mail_config_file)"

  mkdir -p "$(dirname "$config")"

  cat > "$config" <<'EOF'
SMTP_RELAY_ENABLED=0
SMTP_RELAY_HOST=
SMTP_RELAY_PORT=587
SMTP_RELAY_USERNAME=
SMTP_RELAY_PASSWORD=
SMTP_RELAY_SECURITY=tls
EOF

  chmod 0600 "$config"

  mail_apply_relay

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"mail relay disabled"}\n'
  else
    echo "Mail relay disabled"
  fi
}

cron_jobs_dir() {
  echo "/etc/sone/cron/jobs.d"
}

cron_validate_name() {
  local name="$1"

  if [ -z "$name" ]; then
    out_error "--name is required"
  fi

  if ! echo "$name" | grep -Eq '^[A-Za-z0-9_.-]+$'; then
    out_error "invalid cron job name"
  fi
}

cron_status() {
  local installed running enabled

  installed=false
  running=false
  enabled=false

  if command -v cron >/dev/null 2>&1 || systemctl list-unit-files cron.service >/dev/null 2>&1; then
    installed=true
  fi

  if systemctl is-active --quiet cron 2>/dev/null; then
    running=true
  fi

  if systemctl is-enabled --quiet cron 2>/dev/null; then
    enabled=true
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"installed":%s,"running":%s,"enabled":%s,"jobs_dir":"%s"}\n' \
      "$installed" \
      "$running" \
      "$enabled" \
      "$(json_escape "$(cron_jobs_dir)")"
  else
    echo "Installed: $installed"
    echo "Running: $running"
    echo "Enabled: $enabled"
    echo "Jobs dir: $(cron_jobs_dir)"
  fi
}

cron_list() {
  local dir file name size modified first

  dir="$(cron_jobs_dir)"

  mkdir -p "$dir"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"jobs":['

    first=1

    for file in "$dir"/*; do
      [ -f "$file" ] || continue

      name="$(basename "$file")"
      size="$(stat -c '%s' "$file" 2>/dev/null || echo 0)"
      modified="$(stat -c '%y' "$file" 2>/dev/null | cut -d'.' -f1 || true)"

      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '{"name":"%s","path":"%s","size":%s,"modified":"%s","executable":%s}' \
        "$(json_escape "$name")" \
        "$(json_escape "$file")" \
        "$size" \
        "$(json_escape "$modified")" \
        "$([ -x "$file" ] && echo true || echo false)"

      first=0
    done

    printf ']}\n'
  else
    ls -lah "$dir"
  fi
}

cron_create() {
  require_root

  local name command file dir

  name="$(get_arg "--name" "" "${ARGS[@]}")"
  command="$(get_arg "--command" "" "${ARGS[@]}")"

  cron_validate_name "$name"

  if [ -z "$command" ]; then
    out_error "--command is required"
  fi

  dir="$(cron_jobs_dir)"
  file="${dir}/${name}"

  mkdir -p "$dir"

  cat > "$file" <<EOF
#!/usr/bin/env bash
set -e

${command}
EOF

  chmod 0755 "$file"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"cron job created","name":"%s","path":"%s"}\n' \
      "$(json_escape "$name")" \
      "$(json_escape "$file")"
  else
    echo "Cron job created: $name"
  fi
}

cron_delete() {
  require_root

  local name file

  name="$(get_arg "--name" "" "${ARGS[@]}")"

  cron_validate_name "$name"

  file="$(cron_jobs_dir)/${name}"

  if [ ! -f "$file" ]; then
    out_error "cron job not found"
  fi

  rm -f "$file"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"cron job deleted","name":"%s"}\n' \
      "$(json_escape "$name")"
  else
    echo "Cron job deleted: $name"
  fi
}

monitoring_services_file() {
  echo "/etc/sone/monitoring/services.conf"
}

system_status() {
  local hostname_fqdn uptime_seconds load1 load5 load15 kernel os

  hostname_fqdn="$(hostname -f 2>/dev/null || hostname)"
  uptime_seconds="$(cut -d' ' -f1 /proc/uptime 2>/dev/null | cut -d'.' -f1 || echo 0)"
  read -r load1 load5 load15 _ < /proc/loadavg
  kernel="$(uname -r)"
  os="$(. /etc/os-release 2>/dev/null && echo "$PRETTY_NAME")"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"hostname":"%s","uptime_seconds":%s,"load":{"1m":%s,"5m":%s,"15m":%s},"kernel":"%s","os":"%s"}\n' \
      "$(json_escape "$hostname_fqdn")" \
      "$uptime_seconds" \
      "$load1" \
      "$load5" \
      "$load15" \
      "$(json_escape "$kernel")" \
      "$(json_escape "$os")"
  else
    echo "Hostname: $hostname_fqdn"
    echo "Uptime seconds: $uptime_seconds"
    echo "Load: $load1 $load5 $load15"
    echo "Kernel: $kernel"
    echo "OS: $os"
  fi
}

services_status() {
  local services service active enabled first

  if [ -f "$(monitoring_services_file)" ]; then
    services="$(grep -v '^[[:space:]]*$' "$(monitoring_services_file)" | grep -v '^[[:space:]]*#' || true)"
  else
    services="nginx
postfix
fail2ban
cron
redis-server
mysql
mariadb
postgresql"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"services":['
    first=1

    while IFS= read -r service; do
      [ -z "$service" ] && continue

      active=false
      enabled=false

      if systemctl is-active --quiet "$service" 2>/dev/null; then
        active=true
      fi

      if systemctl is-enabled --quiet "$service" 2>/dev/null; then
        enabled=true
      fi

      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '{"name":"%s","active":%s,"enabled":%s}' \
        "$(json_escape "$service")" \
        "$active" \
        "$enabled"

      first=0
    done <<< "$services"

    printf ']}\n'
  else
    while IFS= read -r service; do
      [ -z "$service" ] && continue
      systemctl is-active "$service" 2>/dev/null || true
    done <<< "$services"
  fi
}

memory_status() {
  local mem_total mem_available swap_total swap_free

  mem_total="$(awk '/MemTotal:/ {print $2}' /proc/meminfo)"
  mem_available="$(awk '/MemAvailable:/ {print $2}' /proc/meminfo)"
  swap_total="$(awk '/SwapTotal:/ {print $2}' /proc/meminfo)"
  swap_free="$(awk '/SwapFree:/ {print $2}' /proc/meminfo)"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"memory":{"total_kb":%s,"available_kb":%s,"used_kb":%s},"swap":{"total_kb":%s,"free_kb":%s,"used_kb":%s}}\n' \
      "$mem_total" \
      "$mem_available" \
      "$((mem_total - mem_available))" \
      "$swap_total" \
      "$swap_free" \
      "$((swap_total - swap_free))"
  else
    free -h
  fi
}

disk_status() {
  local first filesystem size used avail percent mount

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"filesystems":['
    first=1

    df -P -B1 | tail -n +2 | while read -r filesystem size used avail percent mount; do
      case "$mount" in
        /proc|/sys|/dev|/run|/snap/*)
          continue
        ;;
      esac

      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '{"filesystem":"%s","size":%s,"used":%s,"available":%s,"use_percent":"%s","mount":"%s"}' \
        "$(json_escape "$filesystem")" \
        "$size" \
        "$used" \
        "$avail" \
        "$(json_escape "$percent")" \
        "$(json_escape "$mount")"

      first=0
    done

    printf ']}\n'
  else
    df -h
  fi
}

cpu_status() {
  local cores model load1 load5 load15

  cores="$(nproc 2>/dev/null || echo 1)"
  model="$(awk -F': ' '/model name/ {print $2; exit}' /proc/cpuinfo 2>/dev/null || true)"
  read -r load1 load5 load15 _ < /proc/loadavg

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"cores":%s,"model":"%s","load":{"1m":%s,"5m":%s,"15m":%s}}\n' \
      "$cores" \
      "$(json_escape "$model")" \
      "$load1" \
      "$load5" \
      "$load15"
  else
    echo "Cores: $cores"
    echo "Model: $model"
    echo "Load: $load1 $load5 $load15"
  fi
}

ports_list() {
  local first proto state local_addr process

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"ports":['
    first=1

    ss -lntup 2>/dev/null | tail -n +2 | while read -r netid state recvq sendq local_addr peer_addr process; do
      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '{"proto":"%s","state":"%s","local":"%s","process":"%s"}' \
        "$(json_escape "$netid")" \
        "$(json_escape "$state")" \
        "$(json_escape "$local_addr")" \
        "$(json_escape "$process")"

      first=0
    done

    printf ']}\n'
  else
    ss -lntup
  fi
}

limits_defaults_file() {
  echo "/etc/sone/limits/defaults.conf"
}

limits_user_file() {
  local user="$1"
  echo "/etc/sone/limits/users.d/${user}.conf"
}

limits_validate_user() {
  local user="$1"

  if [ -z "$user" ]; then
    out_error "--user is required"
  fi

  if ! echo "$user" | grep -Eq '^[A-Za-z0-9._-]+$'; then
    out_error "invalid user"
  fi

  if ! id "$user" >/dev/null 2>&1; then
    out_error "user not found: $user"
  fi
}

limits_load() {
  local user="$1"
  local defaults user_file

  defaults="$(limits_defaults_file)"
  user_file="$(limits_user_file "$user")"

  if [ -f "$defaults" ]; then
    . "$defaults"
  fi

  if [ -f "$user_file" ]; then
    . "$user_file"
  fi
}

limits_write_user_file() {
  local user="$1"
  local file

  file="$(limits_user_file "$user")"

  mkdir -p "$(dirname "$file")"

  cat > "$file" <<EOF
CPU_PERCENT=${CPU_PERCENT}
MEMORY_MAX=${MEMORY_MAX}
TASKS_MAX=${TASKS_MAX}
DISK_MAX=${DISK_MAX}
MAX_SITES=${MAX_SITES}
MAX_DOMAINS=${MAX_DOMAINS}
MAX_DATABASES=${MAX_DATABASES}
MAX_EMAILS=${MAX_EMAILS}
MAX_CRON_JOBS=${MAX_CRON_JOBS}
MAX_BACKUPS=${MAX_BACKUPS}
EOF

  chmod 0644 "$file"
}

limits_json() {
  local user="$1"

  printf '{"cpu_percent":%s,"memory":"%s","tasks":%s,"disk":"%s","sites":%s,"domains":%s,"databases":%s,"emails":%s,"cron_jobs":%s,"backups":%s}' \
    "${CPU_PERCENT:-0}" \
    "$(json_escape "${MEMORY_MAX:-}")" \
    "${TASKS_MAX:-0}" \
    "$(json_escape "${DISK_MAX:-}")" \
    "${MAX_SITES:-0}" \
    "${MAX_DOMAINS:-0}" \
    "${MAX_DATABASES:-0}" \
    "${MAX_EMAILS:-0}" \
    "${MAX_CRON_JOBS:-0}" \
    "${MAX_BACKUPS:-0}"
}

limits_status() {
  local user

  user="$(get_arg "--user" "" "${ARGS[@]}")"

  limits_validate_user "$user"
  limits_load "$user"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"user":"%s","limits":%s}\n' \
      "$(json_escape "$user")" \
      "$(limits_json "$user")"
  else
    limits_json "$user"
    echo
  fi
}

limits_set() {
  require_root

  local user value

  user="$(get_arg "--user" "" "${ARGS[@]}")"

  limits_validate_user "$user"
  limits_load "$user"

  value="$(get_arg "--cpu" "" "${ARGS[@]}")"
  [ -n "$value" ] && CPU_PERCENT="$value"

  value="$(get_arg "--memory" "" "${ARGS[@]}")"
  [ -n "$value" ] && MEMORY_MAX="$value"

  value="$(get_arg "--tasks" "" "${ARGS[@]}")"
  [ -n "$value" ] && TASKS_MAX="$value"

  value="$(get_arg "--disk" "" "${ARGS[@]}")"
  [ -n "$value" ] && DISK_MAX="$value"

  value="$(get_arg "--sites" "" "${ARGS[@]}")"
  [ -n "$value" ] && MAX_SITES="$value"

  value="$(get_arg "--domains" "" "${ARGS[@]}")"
  [ -n "$value" ] && MAX_DOMAINS="$value"

  value="$(get_arg "--databases" "" "${ARGS[@]}")"
  [ -n "$value" ] && MAX_DATABASES="$value"

  value="$(get_arg "--emails" "" "${ARGS[@]}")"
  [ -n "$value" ] && MAX_EMAILS="$value"

  value="$(get_arg "--cron-jobs" "" "${ARGS[@]}")"
  [ -n "$value" ] && MAX_CRON_JOBS="$value"

  value="$(get_arg "--backups" "" "${ARGS[@]}")"
  [ -n "$value" ] && MAX_BACKUPS="$value"

  limits_write_user_file "$user"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"user":"%s","limits":%s}\n' \
      "$(json_escape "$user")" \
      "$(limits_json "$user")"
  else
    echo "Limits updated for $user"
  fi
}

limits_count_sites() {
  local user="$1"
  find "/home/${user}/htdocs" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l
}

limits_count_domains() {
  limits_count_sites "$1"
}

limits_count_databases() {
  local user="$1"
  find "/home/${user}/backups/databases" -type f 2>/dev/null | wc -l
}

limits_count_emails() {
  echo 0
}

limits_count_cron_jobs() {
  find /etc/sone/cron/jobs.d -maxdepth 1 -type f 2>/dev/null | wc -l
}

limits_count_backups() {
  local user="$1"
  find "/home/${user}/backups" -type f 2>/dev/null | wc -l
}

limits_disk_used_bytes() {
  local user="$1"
  du -sb "/home/${user}" 2>/dev/null | awk '{print $1}'
}

limits_usage() {
  local user sites domains databases emails cron_jobs backups disk_used

  user="$(get_arg "--user" "" "${ARGS[@]}")"

  limits_validate_user "$user"

  sites="$(limits_count_sites "$user")"
  domains="$(limits_count_domains "$user")"
  databases="$(limits_count_databases "$user")"
  emails="$(limits_count_emails "$user")"
  cron_jobs="$(limits_count_cron_jobs "$user")"
  backups="$(limits_count_backups "$user")"
  disk_used="$(limits_disk_used_bytes "$user")"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"user":"%s","usage":{"sites":%s,"domains":%s,"databases":%s,"emails":%s,"cron_jobs":%s,"backups":%s,"disk_used_bytes":%s}}\n' \
      "$(json_escape "$user")" \
      "$sites" \
      "$domains" \
      "$databases" \
      "$emails" \
      "$cron_jobs" \
      "$backups" \
      "$disk_used"
  else
    echo "Sites: $sites"
    echo "Domains: $domains"
    echo "Databases: $databases"
    echo "Emails: $emails"
    echo "Cron jobs: $cron_jobs"
    echo "Backups: $backups"
    echo "Disk used bytes: $disk_used"
  fi
}

limits_limit_for_resource() {
  local resource="$1"

  case "$resource" in
    sites)
      echo "${MAX_SITES:-0}"
    ;;
    domains)
      echo "${MAX_DOMAINS:-0}"
    ;;
    databases)
      echo "${MAX_DATABASES:-0}"
    ;;
    emails)
      echo "${MAX_EMAILS:-0}"
    ;;
    cron_jobs)
      echo "${MAX_CRON_JOBS:-0}"
    ;;
    backups)
      echo "${MAX_BACKUPS:-0}"
    ;;
    *)
      out_error "invalid resource"
    ;;
  esac
}

limits_usage_for_resource() {
  local user="$1"
  local resource="$2"

  case "$resource" in
    sites)
      limits_count_sites "$user"
    ;;
    domains)
      limits_count_domains "$user"
    ;;
    databases)
      limits_count_databases "$user"
    ;;
    emails)
      limits_count_emails "$user"
    ;;
    cron_jobs)
      limits_count_cron_jobs "$user"
    ;;
    backups)
      limits_count_backups "$user"
    ;;
    *)
      out_error "invalid resource"
    ;;
  esac
}

limits_check() {
  local user resource limit used allowed

  user="$(get_arg "--user" "" "${ARGS[@]}")"
  resource="$(get_arg "--resource" "" "${ARGS[@]}")"

  limits_validate_user "$user"

  if [ -z "$resource" ]; then
    out_error "--resource is required"
  fi

  limits_load "$user"

  limit="$(limits_limit_for_resource "$resource")"
  used="$(limits_usage_for_resource "$user" "$resource")"

  allowed=true

  if [ "$limit" -gt 0 ] && [ "$used" -ge "$limit" ]; then
    allowed=false
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"user":"%s","resource":"%s","allowed":%s,"used":%s,"limit":%s}\n' \
      "$(json_escape "$user")" \
      "$(json_escape "$resource")" \
      "$allowed" \
      "$used" \
      "$limit"
  else
    echo "Allowed: $allowed"
    echo "Used: $used"
    echo "Limit: $limit"
  fi
}

queue_config_file() {
  echo "/etc/sone/queue/queue.conf"
}

queue_log_file() {
  echo "/var/log/sone/queue/panel-worker.log"
}

queue_status() {
  local supervisor_running raw status_line worker_state

  supervisor_running=false
  raw=""
  status_line=""
  worker_state="unknown"

  if systemctl is-active --quiet supervisor 2>/dev/null; then
    supervisor_running=true
  fi

  if command -v supervisorctl >/dev/null 2>&1; then
    raw="$(supervisorctl status 2>/dev/null || true)"
    status_line="$(echo "$raw" | grep '^sone-panel-worker' | head -n 1 || true)"
    worker_state="$(echo "$status_line" | awk '{print $2}')"
    [ -z "$worker_state" ] && worker_state="unknown"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"supervisor_running":%s,"worker":"sone-panel-worker","worker_state":"%s","raw":"%s"}\n' \
      "$supervisor_running" \
      "$(json_escape "$worker_state")" \
      "$(json_escape "$raw")"
  else
    echo "Supervisor running: $supervisor_running"
    echo "$raw"
  fi
}

queue_restart() {
  require_root

  if ! command -v supervisorctl >/dev/null 2>&1; then
    out_error "supervisorctl is not installed"
  fi

  supervisorctl reread >/dev/null 2>&1 || true
  supervisorctl update >/dev/null 2>&1 || true
  supervisorctl restart 'sone-panel-worker:*' >/dev/null 2>&1 || supervisorctl restart sone-panel-worker >/dev/null 2>&1 || true

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"queue worker restarted"}\n'
  else
    echo "Queue worker restarted"
  fi
}

queue_logs() {
  local lines file content

  lines="$(get_arg "--lines" "100" "${ARGS[@]}")"
  file="$(queue_log_file)"

  if ! echo "$lines" | grep -Eq '^[0-9]+$'; then
    out_error "invalid --lines value"
  fi

  if [ "$lines" -lt 1 ]; then
    out_error "--lines must be greater than 0"
  fi

  if [ "$lines" -gt 2000 ]; then
    out_error "--lines must be less than or equal to 2000"
  fi

  if [ ! -f "$file" ]; then
    if [ "$JSON" -eq 1 ]; then
      printf '{"ok":true,"file":"%s","lines":%s,"content":""}\n' \
        "$(json_escape "$file")" \
        "$lines"
    else
      echo ""
    fi
    return 0
  fi

  content="$(tail -n "$lines" "$file" 2>/dev/null || true)"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"file":"%s","lines":%s,"content":"%s"}\n' \
      "$(json_escape "$file")" \
      "$lines" \
      "$(json_escape "$content")"
  else
    echo "$content"
  fi
}

ssl_acme_dir() {
  echo "/etc/letsencrypt/live"
}

ssl_local_dir() {
  echo "/etc/sone/ssl/local"
}

ssl_log_file() {
  echo "/var/log/sone/ssl/renew.log"
}

ssl_validate_domain() {
  local domain="$1"

  if [ -z "$domain" ]; then
    out_error "--domain is required"
  fi

  if ! echo "$domain" | grep -Eq '^[A-Za-z0-9.-]+$'; then
    out_error "invalid domain"
  fi
}

ssl_cert_end_date() {
  local cert="$1"

  if [ ! -f "$cert" ]; then
    echo ""
    return 0
  fi

  openssl x509 -enddate -noout -in "$cert" 2>/dev/null | cut -d= -f2- || true
}

ssl_cert_subject() {
  local cert="$1"

  if [ ! -f "$cert" ]; then
    echo ""
    return 0
  fi

  openssl x509 -subject -noout -in "$cert" 2>/dev/null | sed 's/^subject=//' || true
}

ssl_status_domain() {
  local domain type cert key exists end_date subject

  domain="$(get_arg "--domain" "" "${ARGS[@]}")"

  ssl_validate_domain "$domain"

  type="none"
  cert=""
  key=""
  exists=false

  if [ -f "$(ssl_acme_dir)/${domain}/fullchain.pem" ]; then
    type="acme"
    cert="$(ssl_acme_dir)/${domain}/fullchain.pem"
    key="$(ssl_acme_dir)/${domain}/privkey.pem"
    exists=true
  elif [ -f "$(ssl_local_dir)/${domain}/fullchain.pem" ]; then
    type="local"
    cert="$(ssl_local_dir)/${domain}/fullchain.pem"
    key="$(ssl_local_dir)/${domain}/privkey.pem"
    exists=true
  fi

  end_date="$(ssl_cert_end_date "$cert")"
  subject="$(ssl_cert_subject "$cert")"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"domain":"%s","exists":%s,"type":"%s","cert":"%s","key":"%s","end_date":"%s","subject":"%s"}\n' \
      "$(json_escape "$domain")" \
      "$exists" \
      "$(json_escape "$type")" \
      "$(json_escape "$cert")" \
      "$(json_escape "$key")" \
      "$(json_escape "$end_date")" \
      "$(json_escape "$subject")"
  else
    echo "Domain: $domain"
    echo "Exists: $exists"
    echo "Type: $type"
    echo "Cert: $cert"
    echo "Key: $key"
    echo "End date: $end_date"
    echo "Subject: $subject"
  fi
}

ssl_list() {
  local first dir domain cert end_date subject type

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"certificates":['
    first=1

    for dir in "$(ssl_acme_dir)"/*; do
      [ -d "$dir" ] || continue
      domain="$(basename "$dir")"
      cert="${dir}/fullchain.pem"
      [ -f "$cert" ] || continue

      end_date="$(ssl_cert_end_date "$cert")"
      subject="$(ssl_cert_subject "$cert")"

      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '{"domain":"%s","type":"acme","cert":"%s","end_date":"%s","subject":"%s"}' \
        "$(json_escape "$domain")" \
        "$(json_escape "$cert")" \
        "$(json_escape "$end_date")" \
        "$(json_escape "$subject")"

      first=0
    done

    for dir in "$(ssl_local_dir)"/*; do
      [ -d "$dir" ] || continue
      domain="$(basename "$dir")"
      cert="${dir}/fullchain.pem"
      [ -f "$cert" ] || continue

      end_date="$(ssl_cert_end_date "$cert")"
      subject="$(ssl_cert_subject "$cert")"

      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '{"domain":"%s","type":"local","cert":"%s","end_date":"%s","subject":"%s"}' \
        "$(json_escape "$domain")" \
        "$(json_escape "$cert")" \
        "$(json_escape "$end_date")" \
        "$(json_escape "$subject")"

      first=0
    done

    printf ']}\n'
  else
    find "$(ssl_acme_dir)" "$(ssl_local_dir)" -name fullchain.pem 2>/dev/null || true
  fi
}

ssl_local_create() {
  require_root

  local domain days dir key cert fullchain

  domain="$(get_arg "--domain" "" "${ARGS[@]}")"
  days="$(get_arg "--days" "3650" "${ARGS[@]}")"

  ssl_validate_domain "$domain"

  if ! echo "$days" | grep -Eq '^[0-9]+$'; then
    out_error "invalid days"
  fi

  dir="$(ssl_local_dir)/${domain}"
  key="${dir}/privkey.pem"
  cert="${dir}/cert.pem"
  fullchain="${dir}/fullchain.pem"

  mkdir -p "$dir"

  openssl req -x509 -nodes -newkey rsa:2048 \
    -days "$days" \
    -keyout "$key" \
    -out "$cert" \
    -subj "/CN=${domain}" \
    >/dev/null 2>/tmp/sone-ssl-error.log || out_error "$(cat /tmp/sone-ssl-error.log)"

  cp "$cert" "$fullchain"

  chmod 0600 "$key"
  chmod 0644 "$cert" "$fullchain"

  if command -v nginx >/dev/null 2>&1; then
    nginx -t >/dev/null 2>&1 && systemctl reload nginx >/dev/null 2>&1 || true
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"local ssl created","domain":"%s","cert":"%s","key":"%s","days":%s}\n' \
      "$(json_escape "$domain")" \
      "$(json_escape "$fullchain")" \
      "$(json_escape "$key")" \
      "$days"
  else
    echo "Local SSL created: $domain"
  fi
}

ssl_local_renew() {
  ssl_local_create
}

ssl_local_delete() {
  require_root

  local domain dir

  domain="$(get_arg "--domain" "" "${ARGS[@]}")"

  ssl_validate_domain "$domain"

  dir="$(ssl_local_dir)/${domain}"

  if [ ! -d "$dir" ]; then
    out_error "local ssl not found"
  fi

  rm -rf "$dir"

  if command -v nginx >/dev/null 2>&1; then
    nginx -t >/dev/null 2>&1 && systemctl reload nginx >/dev/null 2>&1 || true
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"local ssl deleted","domain":"%s"}\n' \
      "$(json_escape "$domain")"
  else
    echo "Local SSL deleted: $domain"
  fi
}

ssl_renew_acme() {
  local domain log_file

  domain="$(get_arg "--domain" "" "${ARGS[@]}")"
  log_file="$(ssl_log_file)"

  mkdir -p "$(dirname "$log_file")"

  if command -v certbot >/dev/null 2>&1; then
    if [ -n "$domain" ]; then
      certbot renew --cert-name "$domain" >> "$log_file" 2>&1 || out_error "acme renew failed"
    else
      certbot renew >> "$log_file" 2>&1 || out_error "acme renew failed"
    fi
  elif command -v acme.sh >/dev/null 2>&1; then
    if [ -n "$domain" ]; then
      acme.sh --renew -d "$domain" >> "$log_file" 2>&1 || out_error "acme renew failed"
    else
      acme.sh --cron >> "$log_file" 2>&1 || out_error "acme renew failed"
    fi
  else
    out_error "no acme client found"
  fi

  if command -v nginx >/dev/null 2>&1; then
    nginx -t >/dev/null 2>&1 && systemctl reload nginx >/dev/null 2>&1 || true
  fi
}

ssl_renew_local_all() {
  local dir domain

  for dir in "$(ssl_local_dir)"/*; do
    [ -d "$dir" ] || continue
    domain="$(basename "$dir")"

    ARGS=("--domain=${domain}" "--days=3650")
    ssl_local_create >/dev/null
  done
}

ssl_renew() {
  require_root

  local type domain

  type="$(get_arg "--type" "acme" "${ARGS[@]}")"
  domain="$(get_arg "--domain" "" "${ARGS[@]}")"

  case "$type" in
    acme)
      ssl_renew_acme
    ;;
    local)
      if [ -n "$domain" ]; then
        ssl_local_create
      else
        ssl_renew_local_all
      fi
    ;;
    all)
      ssl_renew_acme || true
      ssl_renew_local_all || true
    ;;
    *)
      out_error "invalid ssl type"
    ;;
  esac

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"ssl renew finished","type":"%s","domain":"%s"}\n' \
      "$(json_escape "$type")" \
      "$(json_escape "$domain")"
  else
    echo "SSL renew finished"
  fi
}

ssl_logs() {
  local lines file content

  lines="$(get_arg "--lines" "100" "${ARGS[@]}")"
  file="$(ssl_log_file)"

  if ! echo "$lines" | grep -Eq '^[0-9]+$'; then
    out_error "invalid --lines value"
  fi

  if [ "$lines" -gt 2000 ]; then
    out_error "--lines must be less than or equal to 2000"
  fi

  if [ ! -f "$file" ]; then
    content=""
  else
    content="$(tail -n "$lines" "$file" 2>/dev/null || true)"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"file":"%s","lines":%s,"content":"%s"}\n' \
      "$(json_escape "$file")" \
      "$lines" \
      "$(json_escape "$content")"
  else
    echo "$content"
  fi
}

apps_catalog_file() {
  echo "/etc/sone/apps/apps.json"
}

apps_require_catalog() {
  local file

  file="$(apps_catalog_file)"

  if [ ! -f "$file" ]; then
    out_error "apps catalog not found: $file"
  fi

  if ! command -v python3 >/dev/null 2>&1; then
    out_error "python3 is required"
  fi
}

apps_validate_id() {
  local app="$1"

  if [ -z "$app" ]; then
    out_error "--app is required"
  fi

  if ! echo "$app" | grep -Eq '^[A-Za-z0-9._-]+$'; then
    out_error "invalid app id"
  fi
}

app_list() {
  apps_require_catalog

  python3 - <<'PY'
import json

path = "/etc/sone/apps/apps.json"

with open(path, "r", encoding="utf-8") as f:
    data = json.load(f)

apps = []

for app in data.get("apps", []):
    versions = app.get("versions", [])

    apps.append({
        "id": app.get("id", ""),
        "name": app.get("name", ""),
        "type": app.get("type", ""),
        "stack": app.get("stack", ""),
        "installer": app.get("installer", ""),
        "requires_database": bool(app.get("requires_database", False)),
        "database_default": app.get("database_default", "none"),
        "database_allowed": app.get("database_allowed", []),
        "default_php": app.get("default_php", ""),
        "node_default": app.get("node_default", ""),
        "nginx_template": app.get("nginx_template", ""),
        "systemd_template": app.get("systemd_template", ""),
        "versions_count": len(versions)
    })

print(json.dumps({"ok": True, "apps": apps}, ensure_ascii=False))
PY
}

app_versions() {
  local app

  app="$(get_arg "--app" "" "${ARGS[@]}")"

  apps_validate_id "$app"
  apps_require_catalog

  APP_ID="$app" python3 - <<'PY'
import json
import os
import sys

path = "/etc/sone/apps/apps.json"
app_id = os.environ["APP_ID"]

with open(path, "r", encoding="utf-8") as f:
    data = json.load(f)

for app in data.get("apps", []):
    if app.get("id") == app_id:
        print(json.dumps({
            "ok": True,
            "app": app_id,
            "name": app.get("name", ""),
            "versions": app.get("versions", [])
        }, ensure_ascii=False))
        sys.exit(0)

print(json.dumps({"ok": False, "error": f"app not found: {app_id}", "code": 1}, ensure_ascii=False))
sys.exit(1)
PY
}

app_defaults() {
  local app version

  app="$(get_arg "--app" "" "${ARGS[@]}")"
  version="$(get_arg "--version" "latest" "${ARGS[@]}")"

  apps_validate_id "$app"
  apps_require_catalog

  if ! echo "$version" | grep -Eq '^[A-Za-z0-9._-]+$'; then
    out_error "invalid version"
  fi

  APP_ID="$app" APP_VERSION="$version" python3 - <<'PY'
import json
import os
import sys

path = "/etc/sone/apps/apps.json"
app_id = os.environ["APP_ID"]
version_id = os.environ["APP_VERSION"]

with open(path, "r", encoding="utf-8") as f:
    data = json.load(f)

for app in data.get("apps", []):
    if app.get("id") != app_id:
        continue

    selected = None

    for version in app.get("versions", []):
        if version.get("version") == version_id:
            selected = version
            break

    if selected is None:
        print(json.dumps({
            "ok": False,
            "error": f"version not found: {version_id}",
            "code": 1
        }, ensure_ascii=False))
        sys.exit(1)

    result = {
        "ok": True,
        "app": {
            "id": app.get("id", ""),
            "name": app.get("name", ""),
            "type": app.get("type", ""),
            "stack": app.get("stack", ""),
            "installer": app.get("installer", ""),
            "package": app.get("package", ""),
            "runtime": app.get("runtime", ""),
            "nginx_template": app.get("nginx_template", ""),
            "systemd_template": app.get("systemd_template", ""),
            "build_command": app.get("build_command", ""),
            "start_command": app.get("start_command", ""),
            "output_dir": app.get("output_dir", ""),
            "port_mode": app.get("port_mode", "")
        },
        "version": selected.get("version", ""),
        "php": selected.get("php", app.get("default_php", "")),
        "node": selected.get("node", app.get("node_default", "")),
        "composer": selected.get("composer", ""),
        "composer_constraint": selected.get("composer_constraint", ""),
        "legacy": bool(selected.get("legacy", False)),
        "ui_options": app.get("ui_options", []),
        "database": {
            "required": bool(app.get("requires_database", False)),
            "default": app.get("database_default", "none"),
            "allowed": app.get("database_allowed", []),
            "engines": app.get("db_engines", [])
        }
    }

    print(json.dumps(result, ensure_ascii=False))
    sys.exit(0)

print(json.dumps({
    "ok": False,
    "error": f"app not found: {app_id}",
    "code": 1
}, ensure_ascii=False))
sys.exit(1)
PY
}

app_ports_dir() {
  echo "/etc/sone/apps/ports.d"
}

app_rendered_nginx_dir() {
  echo "/etc/nginx/conf.d"
}

app_validate_domain() {
  local domain="$1"

  if [ -z "$domain" ]; then
    out_error "--domain is required"
  fi

  if ! echo "$domain" | grep -Eq '^[A-Za-z0-9.-]+$'; then
    out_error "invalid domain"
  fi
}

app_validate_port() {
  local port="$1"

  if [ -z "$port" ]; then
    out_error "--port is required"
  fi

  if ! echo "$port" | grep -Eq '^[0-9]+$'; then
    out_error "invalid port"
  fi

  if [ "$port" -lt 1024 ] || [ "$port" -gt 65535 ]; then
    out_error "port must be between 1024 and 65535"
  fi
}

app_template_nginx_file() {
  local template="$1"
  echo "/etc/sone/apps/templates/nginx/${template}.conf.tpl"
}

app_template_service_file() {
  local template="$1"
  echo "/etc/sone/apps/templates/systemd-user/${template}.service.tpl"
}

app_safe_domain_name() {
  local domain="$1"
  echo "$domain" | sed 's/[^A-Za-z0-9._-]/_/g'
}

app_get_defaults_json() {
  local app="$1"
  local version="$2"

  APP_ID="$app" APP_VERSION="$version" python3 - <<'PY'
import json
import os
import sys

path = "/etc/sone/apps/apps.json"
app_id = os.environ["APP_ID"]
version_id = os.environ["APP_VERSION"]

with open(path, "r", encoding="utf-8") as f:
    data = json.load(f)

for app in data.get("apps", []):
    if app.get("id") != app_id:
        continue

    selected = None
    for version in app.get("versions", []):
        if version.get("version") == version_id:
            selected = version
            break

    if selected is None:
        print(json.dumps({"ok": False, "error": "version not found", "code": 1}))
        sys.exit(1)

    print(json.dumps({
        "ok": True,
        "app": app,
        "version": selected
    }, ensure_ascii=False))
    sys.exit(0)

print(json.dumps({"ok": False, "error": "app not found", "code": 1}))
sys.exit(1)
PY
}

app_json_field() {
  local json="$1"
  local path="$2"

  JSON_DATA="$json" JSON_PATH="$path" python3 - <<'PY'
import json
import os

data = json.loads(os.environ["JSON_DATA"])
path = os.environ["JSON_PATH"].split(".")

value = data
for part in path:
    if isinstance(value, dict):
        value = value.get(part, "")
    else:
        value = ""
        break

if value is None:
    value = ""

print(value)
PY
}

app_port_is_used() {
  local port="$1"

  if ss -lntup 2>/dev/null | grep -Eq "[:.]${port}[[:space:]]"; then
    return 0
  fi

  if grep -R "APP_PORT=${port}$" "$(app_ports_dir)" >/dev/null 2>&1; then
    return 0
  fi

  return 1
}

app_resolve_node_version() {
  local requested="$1"
  local defaults="$2"
  local version_node app_node

  version_node="$(app_json_field "$defaults" "version.node")"
  app_node="$(app_json_field "$defaults" "app.node_default")"

  if [ -z "$requested" ] || [ "$requested" = "auto" ]; then
    if [ -n "$version_node" ]; then
      echo "$version_node"
      return 0
    fi

    if [ -n "$app_node" ]; then
      echo "$app_node"
      return 0
    fi

    out_error "node version is not defined for app"
  fi

  echo "$requested"
}

app_port_allocate() {
  require_root

  local user domain start end port file

  user="$(get_arg "--user" "" "${ARGS[@]}")"
  domain="$(get_arg "--domain" "" "${ARGS[@]}")"

  app_service_validate_user "$user"
  app_validate_domain "$domain"

  start=3000
  end=3999

  mkdir -p "$(app_ports_dir)"

  for port in $(seq "$start" "$end"); do
    if ! app_port_is_used "$port"; then
      file="$(app_ports_dir)/$(app_safe_domain_name "$domain").conf"

      cat > "$file" <<EOF
USER=${user}
DOMAIN=${domain}
APP_PORT=${port}
EOF

      chmod 0644 "$file"

      if [ "$JSON" -eq 1 ]; then
        printf '{"ok":true,"user":"%s","domain":"%s","port":%s,"file":"%s"}\n' \
          "$(json_escape "$user")" \
          "$(json_escape "$domain")" \
          "$port" \
          "$(json_escape "$file")"
      else
        echo "$port"
      fi

      return 0
    fi
  done

  out_error "no free app port found"
}

app_render_replace_common() {
  local file="$1"
  local user="$2"
  local domain="$3"
  local app_root="$4"
  local site_root="$5"
  local app_port="$6"
  local node_bin="$7"
  local php_bin="$8"
  local php_fpm_socket="$9"
  local env_file="${10}"
  local aliases="${11}"
  local client_max_body_size="${12}"
  local websocket_port="${13}"
  local reverb_port="${14}"

  sed -i \
    -e "s#__USER__#${user}#g" \
    -e "s#__DOMAIN__#${domain}#g" \
    -e "s#__ALIASES__#${aliases}#g" \
    -e "s#__APP_ROOT__#${app_root}#g" \
    -e "s#__SITE_ROOT__#${site_root}#g" \
    -e "s#__APP_PORT__#${app_port}#g" \
    -e "s#__WEBSOCKET_PORT__#${websocket_port}#g" \
    -e "s#__REVERB_PORT__#${reverb_port}#g" \
    -e "s#__NODE_BIN__#${node_bin}#g" \
    -e "s#__PHP_BIN__#${php_bin}#g" \
    -e "s#__PHP_FPM_SOCKET__#${php_fpm_socket}#g" \
    -e "s#__ENV_FILE__#${env_file}#g" \
    -e "s#__CLIENT_MAX_BODY_SIZE__#${client_max_body_size}#g" \
    "$file"

  sed -i \
    -e "s#__SSL_CERTIFICATE_KEY__#ssl_certificate_key /etc/ssl/sone/${domain}/privkey.pem;#g" \
    -e "s#__SSL_CERTIFICATE__#ssl_certificate /etc/ssl/sone/${domain}/fullchain.pem;#g" \
    -e "s#__ACCESS_LOG__#access_log /var/log/nginx/${domain}.access.log;#g" \
    -e "s#__ERROR_LOG__#error_log /var/log/nginx/${domain}.error.log;#g" \
    "$file"

  sed -i \
    -e "s#__QUEUE_CONNECTION__#database#g" \
    -e "s#__QUEUE_NAMES__#default#g" \
    "$file"
}

app_render_nginx() {
  require_root

  local app version user domain port node defaults template template_file output_file
  local nginx_template site_root app_root node_version aliases client_max_body_size
  local websocket_port reverb_port

  app="$(get_arg "--app" "" "${ARGS[@]}")"
  version="$(get_arg "--version" "latest" "${ARGS[@]}")"
  user="$(get_arg "--user" "" "${ARGS[@]}")"
  domain="$(get_arg "--domain" "" "${ARGS[@]}")"
  port="$(get_arg "--port" "" "${ARGS[@]}")"
  aliases="$(get_arg "--aliases" "" "${ARGS[@]}")"
  client_max_body_size="$(get_arg "--client-max-body-size" "64m" "${ARGS[@]}")"
  websocket_port="$(get_arg "--websocket-port" "$port" "${ARGS[@]}")"
  reverb_port="$(get_arg "--reverb-port" "8090" "${ARGS[@]}")"

  apps_validate_id "$app"
  app_service_validate_user "$user"
  app_validate_domain "$domain"
  app_validate_port "$port"
  apps_require_catalog

  defaults="$(app_get_defaults_json "$app" "$version")"

  if [ "$(app_json_field "$defaults" "ok")" != "True" ]; then
    out_error "app defaults not found"
  fi

  nginx_template="$(app_json_field "$defaults" "app.nginx_template")"

  if [ -z "$nginx_template" ]; then
    out_error "nginx template is not defined for app"
  fi

  template_file="$(app_template_nginx_file "$nginx_template")"

  if [ ! -f "$template_file" ]; then
    out_error "nginx template not found: $nginx_template"
  fi

  node="$(get_arg "--node" "auto" "${ARGS[@]}")"
  node_version="$(app_resolve_node_version "$node" "$defaults")"
  node_validate_user_version "$user" "$node_version"

  site_root="/home/${user}/htdocs/${domain}/public"
  app_root="/home/${user}/htdocs/${domain}"
  output_file="$(app_rendered_nginx_dir)/sone-${domain}.conf"

  cp "$template_file" "$output_file"

  app_render_replace_common \
    "$output_file" \
    "$user" \
    "$domain" \
    "$app_root" \
    "$site_root" \
    "$port" \
    "/opt/node/${node_version}/bin/node" \
    "/opt/php/8.3/bin/php" \
    "/run/sone-php/php8.3-fpm.sock" \
    "${app_root}/.env" \
    "$aliases" \
    "$client_max_body_size" \
    "$websocket_port" \
    "$reverb_port"

  nginx -t >/dev/null 2>&1 || out_error "nginx config test failed"

  systemctl reload nginx >/dev/null 2>&1 || true

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"nginx rendered","app":"%s","domain":"%s","file":"%s","template":"%s","port":%s}\n' \
      "$(json_escape "$app")" \
      "$(json_escape "$domain")" \
      "$(json_escape "$output_file")" \
      "$(json_escape "$nginx_template")" \
      "$port"
  else
    echo "$output_file"
  fi
}

app_render_service() {
  require_root

  local app version user domain port node defaults systemd_template template_file service_file
  local node_version app_root service node_bin php_version php_bin php_fpm_socket

  app="$(get_arg "--app" "" "${ARGS[@]}")"
  version="$(get_arg "--version" "latest" "${ARGS[@]}")"
  user="$(get_arg "--user" "" "${ARGS[@]}")"
  domain="$(get_arg "--domain" "" "${ARGS[@]}")"
  port="$(get_arg "--port" "" "${ARGS[@]}")"
  node="$(get_arg "--node" "auto" "${ARGS[@]}")"
  service="$(get_arg "--service" "${app}-${domain}" "${ARGS[@]}")"

  apps_validate_id "$app"
  app_service_validate_user "$user"
  app_validate_domain "$domain"
  app_validate_port "$port"
  app_service_validate_name "$service"
  apps_require_catalog

  defaults="$(app_get_defaults_json "$app" "$version")"
  systemd_template="$(app_json_field "$defaults" "app.systemd_template")"

  if [ -z "$systemd_template" ]; then
    out_error "systemd template is not defined for app"
  fi

  template_file="$(app_template_service_file "$systemd_template")"

  if [ ! -f "$template_file" ]; then
    out_error "systemd template not found: $systemd_template"
  fi

  node_version="$(app_resolve_node_version "$node" "$defaults")"
  node_validate_user_version "$user" "$node_version"

  app_root="/home/${user}/htdocs/${domain}"
  node_bin="$(node_bin_for_user_version "$user" "$node_version")"

  php_version="$(app_json_field "$defaults" "version.php")"
  [ -z "$php_version" ] && php_version="8.3"

  php_bin="/opt/php/${php_version}/bin/php"
  php_fpm_socket="/run/sone-php/php${php_version}-fpm.sock"

  mkdir -p "$(app_service_user_systemd_dir "$user")"

  service_file="$(app_service_file "$user" "$service")"

  cp "$template_file" "$service_file"

  app_render_replace_common \
    "$service_file" \
    "$user" \
    "$domain" \
    "$app_root" \
    "${app_root}/public" \
    "$port" \
    "$node_bin" \
    "$php_bin" \
    "$php_fpm_socket" \
    "${app_root}/.env" \
    "" \
    "64m" \
    "$port" \
    "8090"

  chown -R "$user:$user" "/home/${user}/.config"
  chmod 0644 "$service_file"

  app_service_ensure_user_manager "$user"

  app_service_run_user_systemctl "$user" daemon-reload >/dev/null 2>&1 || true
  app_service_run_user_systemctl "$user" enable "${service}.service" >/dev/null 2>&1 || true

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"service rendered","app":"%s","domain":"%s","service":"%s","file":"%s","template":"%s","node":"%s","node_bin":"%s","port":%s}\n' \
      "$(json_escape "$app")" \
      "$(json_escape "$domain")" \
      "$(json_escape "$service")" \
      "$(json_escape "$service_file")" \
      "$(json_escape "$systemd_template")" \
      "$(json_escape "$node_version")" \
      "$(json_escape "$node_bin")" \
      "$port"
  else
    echo "$service_file"
  fi
}

app_install_validate_ui() {
  local defaults="$1"
  local ui="$2"

  UI_ID="$ui" JSON_DATA="$defaults" python3 - <<'PY'
import json
import os
import sys

data = json.loads(os.environ["JSON_DATA"])
ui_id = os.environ["UI_ID"]

app = data.get("app", {})
options = app.get("ui_options", [])

if not options:
    if ui_id in ("", "none"):
        sys.exit(0)

    print(json.dumps({"ok": False, "error": "ui options are not supported for this app", "code": 1}))
    sys.exit(1)

for option in options:
    if option.get("id") == ui_id:
        sys.exit(0)

print(json.dumps({"ok": False, "error": f"invalid ui option: {ui_id}", "code": 1}))
sys.exit(1)
PY
}

app_install_nuxt() {
  require_root

  local app version user domain ui node port defaults node_version npm_bin app_root service
  local package_manager ui_package app_name npm_path

  app="$(get_arg "--app" "" "${ARGS[@]}")"
  version="$(get_arg "--version" "latest" "${ARGS[@]}")"
  user="$(get_arg "--user" "" "${ARGS[@]}")"
  domain="$(get_arg "--domain" "" "${ARGS[@]}")"
  ui="$(get_arg "--ui" "none" "${ARGS[@]}")"
  node="$(get_arg "--node" "auto" "${ARGS[@]}")"
  port="$(get_arg "--port" "auto" "${ARGS[@]}")"

  apps_validate_id "$app"
  app_service_validate_user "$user"
  app_validate_domain "$domain"
  apps_require_catalog

  if [ "$app" != "nuxt" ]; then
    out_error "this installer supports only nuxt"
  fi

  defaults="$(app_get_defaults_json "$app" "$version")"
  node_version="$(app_resolve_node_version "$node" "$defaults")"

  node_validate_user_version "$user" "$node_version"
  app_install_validate_ui "$defaults" "$ui" || out_error "invalid ui option"

  npm_bin="$(npm_bin_for_user_version "$user" "$node_version")"
  npm_path="$(dirname "$npm_bin")"
  app_root="/home/${user}/htdocs/${domain}"
  service="${app}-${domain}"
  app_name="$(app_safe_domain_name "$domain")"

  if [ "$port" = "auto" ]; then
    port="$(
      ARGS=("--user=${user}" "--domain=${domain}")
      JSON=0
      app_port_allocate
    )"
    JSON=1
  fi

  app_validate_port "$port"

  mkdir -p "$app_root"
  chown -R "$user:$user" "/home/${user}/htdocs"

  if [ ! -f "${app_root}/package.json" ]; then
    mkdir -p "${app_root}"

    cat > "${app_root}/package.json" <<EOF
{
  "name": "${app_name}",
  "private": true,
  "type": "module",
  "scripts": {
    "build": "nuxt build",
    "dev": "nuxt dev",
    "generate": "nuxt generate",
    "preview": "nuxt preview",
    "start": "node .output/server/index.mjs"
  },
  "dependencies": {
    "nuxt": "^4.0.0",
    "vue": "latest"
  },
  "devDependencies": {}
}
EOF

    cat > "${app_root}/nuxt.config.ts" <<'EOF'
export default defineNuxtConfig({
  compatibilityDate: '2025-07-15',
  devtools: { enabled: false }
})
EOF

    cat > "${app_root}/app.vue" <<'EOF'
<template>
  <main>
    <h1>sOne Nuxt app</h1>
    <p>It works.</p>
  </main>
</template>
EOF

    chown -R "$user:$user" "$app_root"
  fi

  sudo -H -u "$user" env \
    HOME="/home/${user}" \
    USER="$user" \
    LOGNAME="$user" \
    SHELL="/bin/bash" \
    CI=1 \
    npm_config_yes=true \
    npm_config_cache="/home/${user}/.npm" \
    PATH="${npm_path}:/usr/local/bin:/usr/bin:/bin" \
    bash -lc "cd '${app_root}' && timeout 900 npm install" \
    >/tmp/sone-app-install.log 2>&1 || out_error "$(cat /tmp/sone-app-install.log)"

  if [ "$ui" != "none" ]; then
    ui_package="$(
      UI_ID="$ui" JSON_DATA="$defaults" python3 - <<'PY'
import json
import os

data = json.loads(os.environ["JSON_DATA"])
ui_id = os.environ["UI_ID"]

for option in data.get("app", {}).get("ui_options", []):
    if option.get("id") == ui_id:
        print(option.get("package", ""))
        break
PY
    )"

    if [ -n "$ui_package" ]; then
      sudo -H -u "$user" env \
        HOME="/home/${user}" \
        USER="$user" \
        LOGNAME="$user" \
        SHELL="/bin/bash" \
        CI=1 \
        npm_config_yes=true \
        npm_config_cache="/home/${user}/.npm" \
        PATH="${npm_path}:/usr/local/bin:/usr/bin:/bin" \
        bash -lc "cd '${app_root}' && timeout 900 npm install ${ui_package}" \
        >/tmp/sone-app-install.log 2>&1 || out_error "$(cat /tmp/sone-app-install.log)"

      if [ "$ui" = "nuxt-ui" ] || [ "$ui" = "nuxt-ui-pro" ]; then
        cat > "${app_root}/nuxt.config.ts" <<'EOF'
export default defineNuxtConfig({
  compatibilityDate: '2025-07-15',
  devtools: { enabled: false },
  modules: ['@nuxt/ui']
})
EOF
        chown "$user:$user" "${app_root}/nuxt.config.ts"
      fi
    fi
  fi

  sudo -H -u "$user" env \
    HOME="/home/${user}" \
    USER="$user" \
    LOGNAME="$user" \
    SHELL="/bin/bash" \
    CI=1 \
    npm_config_yes=true \
    npm_config_cache="/home/${user}/.npm" \
    PATH="${npm_path}:/usr/local/bin:/usr/bin:/bin" \
    bash -lc "cd '${app_root}' && timeout 900 npm run build" \
    >/tmp/sone-app-install.log 2>&1 || out_error "$(cat /tmp/sone-app-install.log)"

    ARGS=("--app=${app}" "--version=${version}" "--user=${user}" "--domain=${domain}" "--port=${port}" "--node=${node_version}" "--service=${service}")
    app_render_service >/dev/null
  
    if [ ! -f "/etc/ssl/sone/${domain}/fullchain.pem" ] || [ ! -f "/etc/ssl/sone/${domain}/privkey.pem" ]; then
    mkdir -p "/etc/ssl/sone/${domain}"
  
    openssl req -x509 -nodes -newkey rsa:2048 \
      -days 3650 \
      -keyout "/etc/ssl/sone/${domain}/privkey.pem" \
      -out "/etc/ssl/sone/${domain}/fullchain.pem" \
      -subj "/CN=${domain}" \
      >/tmp/sone-app-install.log 2>&1 || out_error "$(cat /tmp/sone-app-install.log)"
  
    chmod 0600 "/etc/ssl/sone/${domain}/privkey.pem"
    chmod 0644 "/etc/ssl/sone/${domain}/fullchain.pem"
  fi
  
  ARGS=("--app=${app}" "--version=${version}" "--user=${user}" "--domain=${domain}" "--port=${port}" "--node=${node_version}")
  app_render_nginx >/dev/null

  ARGS=("--user=${user}" "--service=${service}")
  app_service_restart >/dev/null || true

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"app installed","app":"%s","version":"%s","ui":"%s","user":"%s","domain":"%s","node":"%s","port":%s,"service":"%s","root":"%s"}\n' \
      "$(json_escape "$app")" \
      "$(json_escape "$version")" \
      "$(json_escape "$ui")" \
      "$(json_escape "$user")" \
      "$(json_escape "$domain")" \
      "$(json_escape "$node_version")" \
      "$port" \
      "$(json_escape "$service")" \
      "$(json_escape "$app_root")"
  else
    echo "App installed: $domain"
  fi
}

app_install() {
  local app

  app="$(get_arg "--app" "" "${ARGS[@]}")"

  case "$app" in
    nuxt)
      app_install_nuxt
    ;;
    *)
      out_error "installer is not implemented for app: $app"
    ;;
  esac
}

app_service_validate_user() {
  local user="$1"

  if [ -z "$user" ]; then
    out_error "--user is required"
  fi

  if ! echo "$user" | grep -Eq '^[A-Za-z0-9._-]+$'; then
    out_error "invalid user"
  fi

  if ! id "$user" >/dev/null 2>&1; then
    out_error "user not found: $user"
  fi
}

app_service_validate_name() {
  local service="$1"

  if [ -z "$service" ]; then
    out_error "--service is required"
  fi

  if ! echo "$service" | grep -Eq '^[A-Za-z0-9._@-]+$'; then
    out_error "invalid service name"
  fi
}

app_service_validate_template() {
  local template="$1"

  if [ -z "$template" ]; then
    out_error "--template is required"
  fi

  if ! echo "$template" | grep -Eq '^[A-Za-z0-9._-]+$'; then
    out_error "invalid template name"
  fi
}

app_service_user_id() {
  local user="$1"
  id -u "$user"
}

app_service_user_systemd_dir() {
  local user="$1"
  echo "/home/${user}/.config/systemd/user"
}

app_service_file() {
  local user="$1"
  local service="$2"

  echo "$(app_service_user_systemd_dir "$user")/${service}.service"
}

app_service_template_file() {
  local template="$1"

  echo "/etc/sone/apps/templates/systemd-user/${template}.service.tpl"
}

app_service_run_user_systemctl() {
  local user="$1"
  shift

  local uid

  uid="$(app_service_user_id "$user")"

  sudo -u "$user" XDG_RUNTIME_DIR="/run/user/${uid}" systemctl --user "$@"
}

app_service_ensure_linger() {
  local user="$1"

  if [ ! -f "/var/lib/systemd/linger/${user}" ]; then
    loginctl enable-linger "$user" >/dev/null 2>&1 || true
  fi
}

app_service_ensure_user_manager() {
  local user="$1"
  local uid

  uid="$(app_service_user_id "$user")"

  app_service_ensure_linger "$user"

  systemctl start "user@${uid}.service" >/dev/null 2>&1 || true

  mkdir -p "/run/user/${uid}" || true
  chown "$user:$user" "/run/user/${uid}" || true
  chmod 0700 "/run/user/${uid}" || true
}

app_service_create() {
  require_root

  local user service template template_file service_file

  user="$(get_arg "--user" "" "${ARGS[@]}")"
  service="$(get_arg "--service" "" "${ARGS[@]}")"
  template="$(get_arg "--template" "" "${ARGS[@]}")"

  app_service_validate_user "$user"
  app_service_validate_name "$service"
  app_service_validate_template "$template"

  template_file="$(app_service_template_file "$template")"
  service_file="$(app_service_file "$user" "$service")"

  if [ ! -f "$template_file" ]; then
    out_error "service template not found: $template"
  fi

  mkdir -p "$(dirname "$service_file")"

  cp "$template_file" "$service_file"

  chown -R "$user:$user" "/home/${user}/.config"
  chmod 0644 "$service_file"

  app_service_ensure_user_manager "$user"

  app_service_run_user_systemctl "$user" daemon-reload >/dev/null 2>&1 || true
  app_service_run_user_systemctl "$user" enable "${service}.service" >/dev/null 2>&1 || true

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"service created","user":"%s","service":"%s","path":"%s","template":"%s"}\n' \
      "$(json_escape "$user")" \
      "$(json_escape "$service")" \
      "$(json_escape "$service_file")" \
      "$(json_escape "$template")"
  else
    echo "Service created: $service"
  fi
}

app_service_start() {
  require_root

  local user service

  user="$(get_arg "--user" "" "${ARGS[@]}")"
  service="$(get_arg "--service" "" "${ARGS[@]}")"

  app_service_validate_user "$user"
  app_service_validate_name "$service"
  app_service_ensure_user_manager "$user"

  app_service_run_user_systemctl "$user" daemon-reload >/dev/null 2>&1 || true
  app_service_run_user_systemctl "$user" start "${service}.service" >/dev/null 2>&1

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"service started","user":"%s","service":"%s"}\n' \
      "$(json_escape "$user")" \
      "$(json_escape "$service")"
  else
    echo "Service started: $service"
  fi
}

app_service_stop() {
  require_root

  local user service

  user="$(get_arg "--user" "" "${ARGS[@]}")"
  service="$(get_arg "--service" "" "${ARGS[@]}")"

  app_service_validate_user "$user"
  app_service_validate_name "$service"

  app_service_run_user_systemctl "$user" stop "${service}.service" >/dev/null 2>&1 || true

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"service stopped","user":"%s","service":"%s"}\n' \
      "$(json_escape "$user")" \
      "$(json_escape "$service")"
  else
    echo "Service stopped: $service"
  fi
}

app_service_restart() {
  require_root

  local user service

  user="$(get_arg "--user" "" "${ARGS[@]}")"
  service="$(get_arg "--service" "" "${ARGS[@]}")"

  app_service_validate_user "$user"
  app_service_validate_name "$service"
  app_service_ensure_user_manager "$user"

  app_service_run_user_systemctl "$user" daemon-reload >/dev/null 2>&1 || true
  app_service_run_user_systemctl "$user" restart "${service}.service" >/dev/null 2>&1

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"service restarted","user":"%s","service":"%s"}\n' \
      "$(json_escape "$user")" \
      "$(json_escape "$service")"
  else
    echo "Service restarted: $service"
  fi
}

app_service_status() {
  local user service raw active enabled

  user="$(get_arg "--user" "" "${ARGS[@]}")"
  service="$(get_arg "--service" "" "${ARGS[@]}")"

  app_service_validate_user "$user"
  app_service_validate_name "$service"
  app_service_ensure_user_manager "$user"

  raw="$(app_service_run_user_systemctl "$user" status "${service}.service" --no-pager 2>&1 || true)"

  active=false
  enabled=false

  if app_service_run_user_systemctl "$user" is-active --quiet "${service}.service" 2>/dev/null; then
    active=true
  fi

  if app_service_run_user_systemctl "$user" is-enabled --quiet "${service}.service" 2>/dev/null; then
    enabled=true
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"user":"%s","service":"%s","active":%s,"enabled":%s,"raw":"%s"}\n' \
      "$(json_escape "$user")" \
      "$(json_escape "$service")" \
      "$active" \
      "$enabled" \
      "$(json_escape "$raw")"
  else
    echo "$raw"
  fi
}

app_service_logs() {
  local user service lines raw uid

  user="$(get_arg "--user" "" "${ARGS[@]}")"
  service="$(get_arg "--service" "" "${ARGS[@]}")"
  lines="$(get_arg "--lines" "100" "${ARGS[@]}")"

  app_service_validate_user "$user"
  app_service_validate_name "$service"

  if ! echo "$lines" | grep -Eq '^[0-9]+$'; then
    out_error "invalid --lines value"
  fi

  if [ "$lines" -gt 2000 ]; then
    out_error "--lines must be less than or equal to 2000"
  fi

  uid="$(app_service_user_id "$user")"

  app_service_ensure_user_manager "$user"
  
  raw="$(sudo -u "$user" XDG_RUNTIME_DIR="/run/user/${uid}" journalctl --user -u "${service}.service" -n "$lines" --no-pager 2>&1 || true)"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"user":"%s","service":"%s","lines":%s,"content":"%s"}\n' \
      "$(json_escape "$user")" \
      "$(json_escape "$service")" \
      "$lines" \
      "$(json_escape "$raw")"
  else
    echo "$raw"
  fi
}

about_info() {
  local cli_version panel_version os_name os_version architecture kernel license_edition license_key_available

  cli_version="$(package_version sone-cli)"
  panel_version="$(package_version sone-panel)"
  architecture="$(dpkg --print-architecture 2>/dev/null || uname -m)"
  kernel="$(uname -r)"

  os_name=""
  os_version=""

  if [ -f /etc/os-release ]; then
    os_name="$(grep -E '^PRETTY_NAME=' /etc/os-release | cut -d= -f2- | sed 's/^"//; s/"$//')"
    os_version="$(grep -E '^VERSION_CODENAME=' /etc/os-release | cut -d= -f2- || true)"
  fi

  license_edition="community"
  license_key_available="false"

  if [ -f /etc/sone/license.key ]; then
    license_key_available="true"
  fi

  if [ -f /etc/sone/license.edition ]; then
    license_edition="$(cat /etc/sone/license.edition | tr -d '[:space:]')"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"about":{"name":"sOne Panel","product":"sOne Hosting Platform","version":"%s","cli_version":"%s","author":"Sequel.ONE","website":"https://s01.one","support_email":"support@sequel.one","apt_repository":"https://apt.s01.one","distribution":"%s","architecture":"%s","kernel":"%s","os":"%s","license":{"edition":"%s","key_available":%s}}}\n' \
      "$(json_escape "$panel_version")" \
      "$(json_escape "$cli_version")" \
      "$(json_escape "$os_version")" \
      "$(json_escape "$architecture")" \
      "$(json_escape "$kernel")" \
      "$(json_escape "$os_name")" \
      "$(json_escape "$license_edition")" \
      "$license_key_available"
  else
    echo "Name: sOne Panel"
    echo "Product: sOne Hosting Platform"
    echo "Panel version: ${panel_version}"
    echo "CLI version: ${cli_version}"
    echo "Author: Sequel.ONE"
    echo "Website: https://s01.one"
    echo "Support: support@sequel.one"
    echo "APT repository: https://apt.s01.one"
    echo "Distribution: ${os_version}"
    echo "Architecture: ${architecture}"
    echo "Kernel: ${kernel}"
    echo "OS: ${os_name}"
    echo "License edition: ${license_edition}"
    echo "License key available: ${license_key_available}"
  fi
}

license_show() {
  local license_edition license_key_available installation_id key_fingerprint

  license_edition="community"
  license_key_available="false"
  installation_id=""
  key_fingerprint=""

  if [ -f /etc/sone/license.edition ]; then
    license_edition="$(cat /etc/sone/license.edition | tr -d '[:space:]')"
  fi

  if [ -f /etc/sone/installation.id ]; then
    installation_id="$(cat /etc/sone/installation.id | tr -d '[:space:]')"
  fi

  if [ -f /etc/sone/license.key ]; then
    license_key_available="true"

    if command -v sha256sum >/dev/null 2>&1; then
      key_fingerprint="$(sha256sum /etc/sone/license.key | awk '{print $1}')"
    fi
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"license":{"edition":"%s","key_available":%s,"key_fingerprint":"%s","installation_id":"%s","managed_by":"laravel-panel"}}\n' \
      "$(json_escape "$license_edition")" \
      "$license_key_available" \
      "$(json_escape "$key_fingerprint")" \
      "$(json_escape "$installation_id")"
  else
    echo "Edition: ${license_edition}"
    echo "Key available: ${license_key_available}"
    echo "Key fingerprint: ${key_fingerprint}"
    echo "Installation ID: ${installation_id}"
    echo "Managed by: laravel-panel"
  fi
}

db_root_show() {
  require_root

  local type user auth password_available note socket command_hint

  type="$(db_get_type)"
  user=""
  auth=""
  password_available="false"
  note=""
  socket=""
  command_hint=""

  case "$type" in
    mysql|mariadb)
      user="root"
      auth="local_socket_or_passwordless_root"
      socket="/var/run/mysqld/mysqld.sock"
      command_hint="mysql -uroot"

      if [ "$type" = "mariadb" ]; then
        command_hint="mariadb -uroot"
      fi

      note="Root database access is local only. Password is not exposed by sone-cli."
    ;;

    postgresql)
      user="postgres"
      auth="system_user"
      socket="/var/run/postgresql"
      command_hint="sudo -u postgres psql"
      note="PostgreSQL admin access uses the local postgres system user."
    ;;

    *)
      out_error "invalid database type: $type"
    ;;
  esac

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"database_root":{"type":"%s","user":"%s","password_available":%s,"auth":"%s","socket":"%s","command":"%s","note":"%s"}}\n' \
      "$(json_escape "$type")" \
      "$(json_escape "$user")" \
      "$password_available" \
      "$(json_escape "$auth")" \
      "$(json_escape "$socket")" \
      "$(json_escape "$command_hint")" \
      "$(json_escape "$note")"
  else
    echo "Type: ${type}"
    echo "User: ${user}"
    echo "Password available: ${password_available}"
    echo "Auth: ${auth}"
    echo "Socket: ${socket}"
    echo "Command: ${command_hint}"
    echo "Note: ${note}"
  fi
}

node_normalize_version() {
  local version="${1:-}"

  if [ -z "$version" ]; then
    out_error "--version is required"
  fi

  if ! echo "$version" | grep -Eq '^[0-9]+(\.[0-9]+(\.[0-9]+)?)?$|^lts/[-A-Za-z0-9]+$|^node$|^stable$'; then
    out_error "invalid node version: $version"
  fi

  echo "$version"
}

node_user_home() {
  local user="${1:-}"
  local home_dir

  if [ -z "$user" ]; then
    out_error "--user is required"
  fi

  home_dir="$(getent passwd "$user" | cut -d: -f6)"

  if [ -z "$home_dir" ]; then
    out_error "home directory not found for user: $user"
  fi

  echo "$home_dir"
}

node_resolve_user_version_dir() {
  local user="$1"
  local version="$2"
  local home_dir exact_dir matched_dir

  home_dir="$(node_user_home "$user")"

  exact_dir="${home_dir}/.nvm/versions/node/v${version}"

  if [ -d "$exact_dir" ]; then
    echo "$exact_dir"
    return 0
  fi

  matched_dir="$(find "${home_dir}/.nvm/versions/node" -maxdepth 1 -type d -name "v${version}.*" 2>/dev/null | sort -V | tail -n 1)"

  if [ -n "$matched_dir" ]; then
    echo "$matched_dir"
    return 0
  fi

  echo ""
}

node_bin_for_user_version() {
  local user="$1"
  local version="$2"
  local version_dir

  version_dir="$(node_resolve_user_version_dir "$user" "$version")"

  if [ -n "$version_dir" ]; then
    echo "${version_dir}/bin/node"
  else
    echo ""
  fi
}

npm_bin_for_user_version() {
  local user="$1"
  local version="$2"
  local version_dir

  version_dir="$(node_resolve_user_version_dir "$user" "$version")"

  if [ -n "$version_dir" ]; then
    echo "${version_dir}/bin/npm"
  else
    echo ""
  fi
}

node_validate_user_version() {
  local user="$1"
  local version="$2"
  local node_bin npm_bin

  if [ -z "$version" ]; then
    out_error "node version is required"
  fi

  if ! echo "$version" | grep -Eq '^[0-9]+$'; then
    out_error "invalid node version"
  fi

  node_bin="$(node_bin_for_user_version "$user" "$version")"
  npm_bin="$(npm_bin_for_user_version "$user" "$version")"

  if [ -z "$node_bin" ] || [ ! -x "$node_bin" ]; then
    out_error "node ${version} is not installed for user ${user}"
  fi

  if [ -z "$npm_bin" ] || [ ! -x "$npm_bin" ]; then
    out_error "npm for node ${version} is not installed for user ${user}"
  fi
}

node_ensure_profile() {
  local user="${1:-}"
  local home_dir profile_file marker

  user="$(normalize_user "$user")"
  home_dir="$(node_user_home "$user")"
  profile_file="${home_dir}/.bashrc"
  marker="# sOne NVM loader"

  touch "$profile_file"

  if ! grep -q "$marker" "$profile_file"; then
    cat >> "$profile_file" <<'EOF_PROFILE'

# sOne NVM loader
if [ -f /opt/sone/nvm/profile-nvm.sh ]; then
  . /opt/sone/nvm/profile-nvm.sh
fi
EOF_PROFILE
  fi

  chown "$user:$user" "$profile_file"
}

node_install_nvm_for_user() {
  local user="${1:-}"
  local home_dir

  user="$(normalize_user "$user")"
  home_dir="$(node_user_home "$user")"

  require_command curl
  require_command git

  node_ensure_profile "$user"

  if [ ! -s "${home_dir}/.nvm/nvm.sh" ]; then
    sudo -H -u "$user" bash -lc "cd '${home_dir}' && curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash"
  fi

  if [ ! -s "${home_dir}/.nvm/nvm.sh" ]; then
    out_error "nvm installation failed for user: $user"
  fi
}

node_install() {
  require_root

  local user version home_dir node_version npm_version pnpm_version yarn_version

  user="$(normalize_user "$(get_arg "--user" "" "${ARGS[@]}")")"
  version="$(node_normalize_version "$(get_arg "--version" "" "${ARGS[@]}")")"
  home_dir="$(node_user_home "$user")"

  node_install_nvm_for_user "$user"

  if [ "$JSON" -eq 1 ]; then
    sudo -H -u "$user" bash -lc "
      cd '${home_dir}'
      export NVM_DIR=\"${home_dir}/.nvm\"
      . \"${home_dir}/.nvm/nvm.sh\"
      nvm install '${version}'
      nvm alias default '${version}'
      nvm use default
      corepack enable || true
      npm install -g pnpm --force
      npm install -g yarn --force
    " >/tmp/sone-node-install.log 2>&1 || out_error "$(cat /tmp/sone-node-install.log)"
  else
    sudo -H -u "$user" bash -lc "
      cd '${home_dir}'
      export NVM_DIR=\"${home_dir}/.nvm\"
      . \"${home_dir}/.nvm/nvm.sh\"
      nvm install '${version}'
      nvm alias default '${version}'
      nvm use default
      corepack enable || true
      npm install -g pnpm --force
      npm install -g yarn --force
    "
  fi

  node_version="$(sudo -H -u "$user" bash -lc "export NVM_DIR='${home_dir}/.nvm'; . '${home_dir}/.nvm/nvm.sh'; node -v 2>/dev/null || true")"
  npm_version="$(sudo -H -u "$user" bash -lc "export NVM_DIR='${home_dir}/.nvm'; . '${home_dir}/.nvm/nvm.sh'; npm -v 2>/dev/null || true")"
  pnpm_version="$(sudo -H -u "$user" bash -lc "cd '${home_dir}'; export NVM_DIR='${home_dir}/.nvm'; . '${home_dir}/.nvm/nvm.sh'; export PATH=\"\$(npm bin -g):\$PATH\"; pnpm -v 2>/dev/null || true")"
  yarn_version="$(sudo -H -u "$user" bash -lc "cd '${home_dir}'; export NVM_DIR='${home_dir}/.nvm'; . '${home_dir}/.nvm/nvm.sh'; export PATH=\"\$(npm bin -g):\$PATH\"; yarn -v 2>/dev/null || true")"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"node installed","user":"%s","requested_version":"%s","node":"%s","npm":"%s","pnpm":"%s","yarn":"%s"}\n' \
      "$(json_escape "$user")" \
      "$(json_escape "$version")" \
      "$(json_escape "$node_version")" \
      "$(json_escape "$npm_version")" \
      "$(json_escape "$pnpm_version")" \
      "$(json_escape "$yarn_version")"
  else
    echo "Node installed for user: $user"
    echo "Requested version: $version"
    echo "Node: $node_version"
    echo "npm: $npm_version"
    echo "pnpm: $pnpm_version"
    echo "yarn: $yarn_version"
  fi
}

node_default() {
  require_root

  local user version home_dir node_version

  user="$(normalize_user "$(get_arg "--user" "" "${ARGS[@]}")")"
  version="$(node_normalize_version "$(get_arg "--version" "" "${ARGS[@]}")")"
  home_dir="$(node_user_home "$user")"

  if [ ! -s "${home_dir}/.nvm/nvm.sh" ]; then
    out_error "nvm not installed for user: $user"
  fi

  sudo -H -u "$user" bash -lc "
    export NVM_DIR=\"${home_dir}/.nvm\"
    . \"${home_dir}/.nvm/nvm.sh\"
    nvm alias default '${version}'
    nvm use default
  "

  node_version="$(sudo -H -u "$user" bash -lc "export NVM_DIR='${home_dir}/.nvm'; . '${home_dir}/.nvm/nvm.sh'; node -v 2>/dev/null || true")"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"default node changed","user":"%s","requested_version":"%s","node":"%s"}\n' \
      "$(json_escape "$user")" \
      "$(json_escape "$version")" \
      "$(json_escape "$node_version")"
  else
    echo "Default Node changed for user: $user"
    echo "Requested version: $version"
    echo "Node: $node_version"
  fi
}

node_list() {
  local user home_dir node_dir version current_node current_npm first node_bin npm_bin

  user="$(normalize_user "$(get_arg "--user" "" "${ARGS[@]}")")"
  home_dir="$(node_user_home "$user")"

  if [ ! -d "${home_dir}/.nvm/versions/node" ]; then
    if [ "$JSON" -eq 1 ]; then
      printf '{"ok":true,"user":"%s","nvm_installed":false,"versions":[]}\n' \
        "$(json_escape "$user")"
    else
      echo "NVM is not installed for user: $user"
    fi
    return 0
  fi

  current_node="$(sudo -H -u "$user" bash -lc "export NVM_DIR='${home_dir}/.nvm'; . '${home_dir}/.nvm/nvm.sh'; node -v 2>/dev/null || true")"
  current_npm="$(sudo -H -u "$user" bash -lc "export NVM_DIR='${home_dir}/.nvm'; . '${home_dir}/.nvm/nvm.sh'; npm -v 2>/dev/null || true")"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"user":"%s","nvm_installed":true,"current":{"node":"%s","npm":"%s"},"versions":[' \
      "$(json_escape "$user")" \
      "$(json_escape "$current_node")" \
      "$(json_escape "$current_npm")"

    first=1

    for node_dir in "${home_dir}/.nvm/versions/node"/v*; do
      [ -d "$node_dir" ] || continue

      version="$(basename "$node_dir" | sed 's/^v//')"
      node_bin="${node_dir}/bin/node"
      npm_bin="${node_dir}/bin/npm"

      [ -x "$node_bin" ] || continue

      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '{"version":"%s","major":"%s","node":"%s","npm":"%s"}' \
        "$(json_escape "$version")" \
        "$(json_escape "$(echo "$version" | cut -d. -f1)")" \
        "$(json_escape "$node_bin")" \
        "$(json_escape "$npm_bin")"

      first=0
    done

    printf ']}\n'
  else
    find "${home_dir}/.nvm/versions/node" -maxdepth 1 -type d -name 'v*' 2>/dev/null | sort -V
  fi
}

node_remove() {
  require_root

  local user version home_dir

  user="$(normalize_user "$(get_arg "--user" "" "${ARGS[@]}")")"
  version="$(node_normalize_version "$(get_arg "--version" "" "${ARGS[@]}")")"
  home_dir="$(node_user_home "$user")"

  if [ ! -s "${home_dir}/.nvm/nvm.sh" ]; then
    out_error "nvm not installed for user: $user"
  fi

  sudo -H -u "$user" bash -lc "
    export NVM_DIR=\"${home_dir}/.nvm\"
    . \"${home_dir}/.nvm/nvm.sh\"
    nvm uninstall '${version}'
  "

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"node removed","user":"%s","version":"%s"}\n' \
      "$(json_escape "$user")" \
      "$(json_escape "$version")"
  else
    echo "Node removed for user: $user"
    echo "Version: $version"
  fi
}

bun_install() {
  require_root

  local user home_dir bun_version

  user="$(normalize_user "$(get_arg "--user" "" "${ARGS[@]}")")"
  home_dir="$(node_user_home "$user")"

  require_command curl

  node_ensure_profile "$user"

  sudo -H -u "$user" bash -lc "cd '${home_dir}' && curl -fsSL https://bun.sh/install | bash"

  bun_version="$(sudo -H -u "$user" bash -lc "'${home_dir}/.bun/bin/bun' --version 2>/dev/null || true")"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"bun installed","user":"%s","bun":"%s"}\n' \
      "$(json_escape "$user")" \
      "$(json_escape "$bun_version")"
  else
    echo "Bun installed for user: $user"
    echo "Bun: $bun_version"
  fi
}

powerdns_configure() {
  require_root

  local role master_ip secondary_ip api_key mysql_password template target sqlite_schema

  role="$(get_arg "--role" "" "${ARGS[@]}")"
  master_ip="$(get_arg "--master-ip" "" "${ARGS[@]}")"
  secondary_ip="$(get_arg "--secondary-ip" "" "${ARGS[@]}")"
  api_key="$(get_arg "--api-key" "" "${ARGS[@]}")"
  mysql_password="$(get_arg "--mysql-password" "" "${ARGS[@]}")"

  case "$role" in
    primary|secondary) ;;
    *) out_error "--role must be primary or secondary" ;;
  esac

  [ -z "$master_ip" ] && out_error "--master-ip is required"
  [ -z "$secondary_ip" ] && out_error "--secondary-ip is required"
  [ -z "$api_key" ] && out_error "--api-key is required"

  mkdir -p /etc/sone/powerdns
  mkdir -p /var/lib/powerdns

  target="/etc/powerdns/pdns.conf"

  if [ "$role" = "primary" ]; then
    template="/usr/share/sone/powerdns/pdns-primary.conf.tpl"

    if [ -z "$mysql_password" ]; then
      mysql_password="$(openssl rand -base64 24 | tr -d '=+/')"
    fi

    if command -v mariadb >/dev/null 2>&1; then
      mariadb -uroot -e "CREATE DATABASE IF NOT EXISTS powerdns CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
      mariadb -uroot -e "CREATE USER IF NOT EXISTS 'pdns'@'localhost' IDENTIFIED BY '${mysql_password}';"
      mariadb -uroot -e "ALTER USER 'pdns'@'localhost' IDENTIFIED BY '${mysql_password}';"
      mariadb -uroot -e "GRANT ALL PRIVILEGES ON powerdns.* TO 'pdns'@'localhost'; FLUSH PRIVILEGES;"
    elif command -v mysql >/dev/null 2>&1; then
      mysql -uroot -e "CREATE DATABASE IF NOT EXISTS powerdns CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
      mysql -uroot -e "CREATE USER IF NOT EXISTS 'pdns'@'localhost' IDENTIFIED BY '${mysql_password}';"
      mysql -uroot -e "ALTER USER 'pdns'@'localhost' IDENTIFIED BY '${mysql_password}';"
      mysql -uroot -e "GRANT ALL PRIVILEGES ON powerdns.* TO 'pdns'@'localhost'; FLUSH PRIVILEGES;"
    else
      out_error "mysql/mariadb client not found. Install sone-mysql or sone-mariadb"
    fi

    sed \
      -e "s|__MASTER_IP__|${master_ip}|g" \
      -e "s|__SECONDARY_IP__|${secondary_ip}|g" \
      -e "s|__API_KEY__|${api_key}|g" \
      -e "s|__MYSQL_PASSWORD__|${mysql_password}|g" \
      "$template" > "$target"
  else
    template="/usr/share/sone/powerdns/pdns-secondary.conf.tpl"

    sed \
      -e "s|__MASTER_IP__|${master_ip}|g" \
      -e "s|__SECONDARY_IP__|${secondary_ip}|g" \
      -e "s|__API_KEY__|${api_key}|g" \
      "$template" > "$target"

    sqlite_schema=""
    for candidate in \
      /usr/share/pdns-backend-sqlite3/schema/schema.sqlite3.sql \
      /usr/share/doc/pdns-backend-sqlite3/schema.sqlite3.sql \
      /usr/share/pdns-backend-sqlite3/schema/schema.sql
    do
      if [ -f "$candidate" ]; then
        sqlite_schema="$candidate"
        break
      fi
    done

    if [ ! -f /var/lib/powerdns/pdns.sqlite3 ]; then
      if [ -n "$sqlite_schema" ]; then
        sqlite3 /var/lib/powerdns/pdns.sqlite3 < "$sqlite_schema"
      else
        sqlite3 /var/lib/powerdns/pdns.sqlite3 'CREATE TABLE domains (id INTEGER PRIMARY KEY, name VARCHAR(255) NOT NULL, master VARCHAR(128) DEFAULT NULL, last_check INTEGER DEFAULT NULL, type VARCHAR(6) NOT NULL, notified_serial INTEGER DEFAULT NULL, account VARCHAR(40) DEFAULT NULL);'
      fi

      chown pdns:pdns /var/lib/powerdns/pdns.sqlite3 || true
      chmod 640 /var/lib/powerdns/pdns.sqlite3
    fi
  fi

  cat > /etc/sone/powerdns/powerdns.env <<EOF
SONE_PDNS_ROLE=${role}
SONE_PDNS_MASTER_IP=${master_ip}
SONE_PDNS_SECONDARY_IP=${secondary_ip}
SONE_PDNS_API_KEY=${api_key}
SONE_PDNS_MYSQL_PASSWORD=${mysql_password}
EOF
  chmod 600 /etc/sone/powerdns/powerdns.env

  systemctl restart pdns

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"powerdns configured","role":"%s","master_ip":"%s","secondary_ip":"%s","config":"/etc/powerdns/pdns.conf"}\n' \
      "$(json_escape "$role")" \
      "$(json_escape "$master_ip")" \
      "$(json_escape "$secondary_ip")"
  else
    echo "PowerDNS configured"
    echo "Role: $role"
    echo "Master IP: $master_ip"
    echo "Secondary IP: $secondary_ip"
    echo "Config: /etc/powerdns/pdns.conf"
  fi
}

powerdns_status() {
  local role master_ip secondary_ip status

  role=""
  master_ip=""
  secondary_ip=""

  if [ -f /etc/sone/powerdns/powerdns.env ]; then
    # shellcheck disable=SC1091
    source /etc/sone/powerdns/powerdns.env
    role="${SONE_PDNS_ROLE:-}"
    master_ip="${SONE_PDNS_MASTER_IP:-}"
    secondary_ip="${SONE_PDNS_SECONDARY_IP:-}"
  fi

  status="unknown"
  if systemctl is-active --quiet pdns; then
    status="active"
  else
    status="inactive"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"powerdns":{"status":"%s","role":"%s","master_ip":"%s","secondary_ip":"%s","api":"http://127.0.0.1:8081"}}\n' \
      "$(json_escape "$status")" \
      "$(json_escape "$role")" \
      "$(json_escape "$master_ip")" \
      "$(json_escape "$secondary_ip")"
  else
    echo "Status: $status"
    echo "Role: $role"
    echo "Master IP: $master_ip"
    echo "Secondary IP: $secondary_ip"
    echo "API: http://127.0.0.1:8081"
  fi
}

normalize_dns_zone() {
  local zone="${1:-}"

  if [ -z "$zone" ]; then
    out_error "--zone is required"
  fi

  zone="$(echo "$zone" | tr '[:upper:]' '[:lower:]')"

  if ! echo "$zone" | grep -Eq '^[a-z0-9.-]+$'; then
    out_error "invalid zone: $zone"
  fi

  zone="${zone%.}"

  echo "$zone"
}

normalize_dns_name() {
  local name="${1:-}"

  if [ -z "$name" ]; then
    out_error "--name is required"
  fi

  name="$(echo "$name" | tr '[:upper:]' '[:lower:]')"

  if [ "$name" = "@" ]; then
    echo ""
    return 0
  fi

  if ! echo "$name" | grep -Eq '^[a-z0-9._*-]+$'; then
    out_error "invalid record name: $name"
  fi

  echo "$name"
}

normalize_dns_type() {
  local type="${1:-}"

  if [ -z "$type" ]; then
    out_error "--type is required"
  fi

  type="$(echo "$type" | tr '[:lower:]' '[:upper:]')"

  case "$type" in
    A|AAAA|CNAME|MX|TXT|NS|SRV|CAA|PTR)
      echo "$type"
    ;;
    *)
      out_error "unsupported DNS record type: $type"
    ;;
  esac
}

normalize_ttl() {
  local ttl="${1:-3600}"

  if ! echo "$ttl" | grep -Eq '^[0-9]+$'; then
    out_error "invalid ttl: $ttl"
  fi

  echo "$ttl"
}

powerdns_find_schema() {
  local backend="${1:-}"

  case "$backend" in
    sqlite)
      for candidate in \
        /usr/share/pdns-backend-sqlite3/schema/schema.sqlite3.sql \
        /usr/share/doc/pdns-backend-sqlite3/schema.sqlite3.sql \
        /usr/share/pdns-backend-sqlite3/schema/schema.sql
      do
        if [ -f "$candidate" ]; then
          echo "$candidate"
          return 0
        fi
      done
    ;;
    mysql)
      for candidate in \
        /usr/share/pdns-backend-mysql/schema/schema.mysql.sql \
        /usr/share/doc/pdns-backend-mysql/schema.mysql.sql \
        /usr/share/pdns-backend-mysql/schema/schema.sql
      do
        if [ -f "$candidate" ]; then
          echo "$candidate"
          return 0
        fi
      done
    ;;
  esac

  echo ""
}

powerdns_configure() {
  require_root

  local role backend master_ip secondary_ip api_key mysql_password
  local target template schema mysql_client

  role="$(get_arg "--role" "" "${ARGS[@]}")"
  backend="$(get_arg "--backend" "sqlite" "${ARGS[@]}")"
  master_ip="$(get_arg "--master-ip" "" "${ARGS[@]}")"
  secondary_ip="$(get_arg "--secondary-ip" "" "${ARGS[@]}")"
  api_key="$(get_arg "--api-key" "" "${ARGS[@]}")"
  mysql_password="$(get_arg "--mysql-password" "" "${ARGS[@]}")"

  case "$role" in
    primary|secondary) ;;
    *) out_error "--role must be primary or secondary" ;;
  esac

  case "$backend" in
    sqlite|mysql) ;;
    *) out_error "--backend must be sqlite or mysql" ;;
  esac

  [ -z "$master_ip" ] && out_error "--master-ip is required"
  [ -z "$secondary_ip" ] && out_error "--secondary-ip is required"
  [ -z "$api_key" ] && out_error "--api-key is required"

  require_command pdnsutil

  mkdir -p /etc/sone/powerdns
  mkdir -p /etc/powerdns
  mkdir -p /var/lib/powerdns

  target="/etc/powerdns/pdns.conf"

  if [ "$backend" = "sqlite" ]; then
    schema="$(powerdns_find_schema sqlite)"

    if [ ! -f /var/lib/powerdns/pdns.sqlite3 ]; then
      [ -z "$schema" ] && out_error "PowerDNS sqlite schema not found"

      sqlite3 /var/lib/powerdns/pdns.sqlite3 < "$schema"
      chown pdns:pdns /var/lib/powerdns/pdns.sqlite3 || true
      chmod 640 /var/lib/powerdns/pdns.sqlite3
    fi

    cat > "$target" <<EOF
local-address=$([ "$role" = "primary" ] && echo "$master_ip" || echo "$secondary_ip")
local-port=53

launch=gsqlite3
gsqlite3-database=/var/lib/powerdns/pdns.sqlite3

primary=$([ "$role" = "primary" ] && echo "yes" || echo "no")
secondary=$([ "$role" = "secondary" ] && echo "yes" || echo "no")

allow-axfr-ips=${secondary_ip}
also-notify=${secondary_ip}
allow-notify-from=${master_ip}
allow-unsigned-notify=yes

api=yes
api-key=${api_key}

webserver=yes
webserver-address=127.0.0.1
webserver-allow-from=127.0.0.1,::1
webserver-port=8081

default-soa-content=ns1.s01.one hostmaster.s01.one 0 10800 3600 604800 3600
default-ttl=3600
loglevel=6
security-poll-suffix=
include-dir=/etc/powerdns/pdns.d
EOF
  else
    schema="$(powerdns_find_schema mysql)"

    if command -v mariadb >/dev/null 2>&1; then
      mysql_client="mariadb"
    elif command -v mysql >/dev/null 2>&1; then
      mysql_client="mysql"
    else
      out_error "mysql/mariadb client not found. Install sone-mysql or sone-mariadb"
    fi

    if [ -z "$mysql_password" ]; then
      mysql_password="$(openssl rand -base64 24 | tr -d '=+/')"
    fi

    "$mysql_client" -uroot -e "CREATE DATABASE IF NOT EXISTS powerdns CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
    "$mysql_client" -uroot -e "CREATE USER IF NOT EXISTS 'pdns'@'localhost' IDENTIFIED BY '${mysql_password}';"
    "$mysql_client" -uroot -e "ALTER USER 'pdns'@'localhost' IDENTIFIED BY '${mysql_password}';"
    "$mysql_client" -uroot -e "GRANT ALL PRIVILEGES ON powerdns.* TO 'pdns'@'localhost'; FLUSH PRIVILEGES;"

    if [ -n "$schema" ]; then
      "$mysql_client" -uroot powerdns < "$schema" || true
    fi

    cat > "$target" <<EOF
local-address=$([ "$role" = "primary" ] && echo "$master_ip" || echo "$secondary_ip")
local-port=53

launch=gmysql
gmysql-host=127.0.0.1
gmysql-dbname=powerdns
gmysql-user=pdns
gmysql-password=${mysql_password}

primary=$([ "$role" = "primary" ] && echo "yes" || echo "no")
secondary=$([ "$role" = "secondary" ] && echo "yes" || echo "no")

allow-axfr-ips=${secondary_ip}
also-notify=${secondary_ip}
allow-notify-from=${master_ip}
allow-unsigned-notify=yes

api=yes
api-key=${api_key}

webserver=yes
webserver-address=127.0.0.1
webserver-allow-from=127.0.0.1,::1
webserver-port=8081

default-soa-content=ns1.s01.one hostmaster.s01.one 0 10800 3600 604800 3600
default-ttl=3600
loglevel=6
security-poll-suffix=
include-dir=/etc/powerdns/pdns.d
EOF
  fi

  cat > /etc/sone/powerdns/powerdns.env <<EOF
SONE_PDNS_ROLE=${role}
SONE_PDNS_BACKEND=${backend}
SONE_PDNS_MASTER_IP=${master_ip}
SONE_PDNS_SECONDARY_IP=${secondary_ip}
SONE_PDNS_API_KEY=${api_key}
SONE_PDNS_MYSQL_PASSWORD=${mysql_password}
EOF

  chmod 600 /etc/sone/powerdns/powerdns.env

  systemctl restart pdns

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"powerdns configured","role":"%s","backend":"%s","master_ip":"%s","secondary_ip":"%s","config":"/etc/powerdns/pdns.conf"}\n' \
      "$(json_escape "$role")" \
      "$(json_escape "$backend")" \
      "$(json_escape "$master_ip")" \
      "$(json_escape "$secondary_ip")"
  else
    echo "PowerDNS configured"
    echo "Role: $role"
    echo "Backend: $backend"
    echo "Master IP: $master_ip"
    echo "Secondary IP: $secondary_ip"
    echo "Config: /etc/powerdns/pdns.conf"
  fi
}

powerdns_status() {
  local role backend master_ip secondary_ip status

  role=""
  backend=""
  master_ip=""
  secondary_ip=""

  if [ -f /etc/sone/powerdns/powerdns.env ]; then
    # shellcheck disable=SC1091
    source /etc/sone/powerdns/powerdns.env
    role="${SONE_PDNS_ROLE:-}"
    backend="${SONE_PDNS_BACKEND:-}"
    master_ip="${SONE_PDNS_MASTER_IP:-}"
    secondary_ip="${SONE_PDNS_SECONDARY_IP:-}"
  fi

  status="inactive"
  if systemctl is-active --quiet pdns; then
    status="active"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"powerdns":{"status":"%s","role":"%s","backend":"%s","master_ip":"%s","secondary_ip":"%s","api":"http://127.0.0.1:8081"}}\n' \
      "$(json_escape "$status")" \
      "$(json_escape "$role")" \
      "$(json_escape "$backend")" \
      "$(json_escape "$master_ip")" \
      "$(json_escape "$secondary_ip")"
  else
    echo "Status: $status"
    echo "Role: $role"
    echo "Backend: $backend"
    echo "Master IP: $master_ip"
    echo "Secondary IP: $secondary_ip"
    echo "API: http://127.0.0.1:8081"
  fi
}

dns_zone_list() {
  require_command pdnsutil

  local output

  output="$(pdnsutil list-all-zones 2>/dev/null || true)"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"zones":['

    local first=1
    local zone

    while IFS= read -r zone; do
      [ -z "$zone" ] && continue

      if [ "$first" -eq 0 ]; then
        printf ','
      fi

      printf '"%s"' "$(json_escape "$zone")"
      first=0
    done <<< "$output"

    printf ']}\n'
  else
    printf '%s\n' "$output"
  fi
}

dns_zone_create() {
  require_root
  require_command pdnsutil

  local zone kind master_ip

  zone="$(normalize_dns_zone "$(get_arg "--zone" "" "${ARGS[@]}")")"
  kind="$(get_arg "--kind" "primary" "${ARGS[@]}")"
  master_ip="$(get_arg "--master-ip" "" "${ARGS[@]}")"

  case "$kind" in
    primary|secondary) ;;
    *) out_error "--kind must be primary or secondary" ;;
  esac

  if [ "$kind" = "primary" ]; then
    pdnsutil create-zone "$zone" "ns1.${zone}"
    pdnsutil add-record "$zone" "@" NS 3600 "ns2.${zone}" || true
  else
    [ -z "$master_ip" ] && out_error "--master-ip is required for secondary zone"
    pdnsutil create-secondary-zone "$zone" "$master_ip"
  fi

  pdnsutil rectify-zone "$zone" >/dev/null 2>&1 || true

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"dns zone created","zone":"%s","kind":"%s"}\n' \
      "$(json_escape "$zone")" \
      "$(json_escape "$kind")"
  else
    echo "DNS zone created: $zone"
    echo "Kind: $kind"
  fi
}

dns_zone_delete() {
  require_root
  require_command pdnsutil

  local zone

  zone="$(normalize_dns_zone "$(get_arg "--zone" "" "${ARGS[@]}")")"

  if ! has_flag "--force" "${ARGS[@]}"; then
    out_error "--force is required"
  fi

  pdnsutil delete-zone "$zone"

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"dns zone deleted","zone":"%s"}\n' "$(json_escape "$zone")"
  else
    echo "DNS zone deleted: $zone"
  fi
}

dns_record_add() {
  require_root
  require_command pdnsutil

  local zone name type content ttl

  zone="$(normalize_dns_zone "$(get_arg "--zone" "" "${ARGS[@]}")")"
  name="$(normalize_dns_name "$(get_arg "--name" "" "${ARGS[@]}")")"
  type="$(normalize_dns_type "$(get_arg "--type" "" "${ARGS[@]}")")"
  content="$(get_arg "--content" "" "${ARGS[@]}")"
  ttl="$(normalize_ttl "$(get_arg "--ttl" "3600" "${ARGS[@]}")")"

  [ -z "$content" ] && out_error "--content is required"

  if [ -z "$name" ]; then
    name="@"
  fi

  pdnsutil add-record "$zone" "$name" "$type" "$ttl" "$content"
  pdnsutil rectify-zone "$zone" >/dev/null 2>&1 || true

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"dns record added","zone":"%s","name":"%s","type":"%s","content":"%s","ttl":"%s"}\n' \
      "$(json_escape "$zone")" \
      "$(json_escape "$name")" \
      "$(json_escape "$type")" \
      "$(json_escape "$content")" \
      "$(json_escape "$ttl")"
  else
    echo "DNS record added"
    echo "Zone: $zone"
    echo "Name: $name"
    echo "Type: $type"
    echo "Content: $content"
    echo "TTL: $ttl"
  fi
}

dns_record_replace() {
  require_root
  require_command pdnsutil

  local zone name type content ttl

  zone="$(normalize_dns_zone "$(get_arg "--zone" "" "${ARGS[@]}")")"
  name="$(normalize_dns_name "$(get_arg "--name" "" "${ARGS[@]}")")"
  type="$(normalize_dns_type "$(get_arg "--type" "" "${ARGS[@]}")")"
  content="$(get_arg "--content" "" "${ARGS[@]}")"
  ttl="$(normalize_ttl "$(get_arg "--ttl" "3600" "${ARGS[@]}")")"

  [ -z "$content" ] && out_error "--content is required"

  if [ -z "$name" ]; then
    name="@"
  fi

  pdnsutil replace-rrset "$zone" "$name" "$type" "$ttl" "$content"
  pdnsutil rectify-zone "$zone" >/dev/null 2>&1 || true

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"dns record replaced","zone":"%s","name":"%s","type":"%s","content":"%s","ttl":"%s"}\n' \
      "$(json_escape "$zone")" \
      "$(json_escape "$name")" \
      "$(json_escape "$type")" \
      "$(json_escape "$content")" \
      "$(json_escape "$ttl")"
  else
    echo "DNS record replaced"
    echo "Zone: $zone"
    echo "Name: $name"
    echo "Type: $type"
    echo "Content: $content"
    echo "TTL: $ttl"
  fi
}

dns_record_delete() {
  require_root
  require_command pdnsutil

  local zone name type

  zone="$(normalize_dns_zone "$(get_arg "--zone" "" "${ARGS[@]}")")"
  name="$(normalize_dns_name "$(get_arg "--name" "" "${ARGS[@]}")")"
  type="$(normalize_dns_type "$(get_arg "--type" "" "${ARGS[@]}")")"

  if ! has_flag "--force" "${ARGS[@]}"; then
    out_error "--force is required"
  fi

  if [ -z "$name" ]; then
    name="@"
  fi

  pdnsutil delete-rrset "$zone" "$name" "$type"
  pdnsutil rectify-zone "$zone" >/dev/null 2>&1 || true

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"message":"dns record deleted","zone":"%s","name":"%s","type":"%s"}\n' \
      "$(json_escape "$zone")" \
      "$(json_escape "$name")" \
      "$(json_escape "$type")"
  else
    echo "DNS record deleted"
    echo "Zone: $zone"
    echo "Name: $name"
    echo "Type: $type"
  fi
}

dns_zone_show() {
  require_command pdnsutil

  local zone output

  zone="$(normalize_dns_zone "$(get_arg "--zone" "" "${ARGS[@]}")")"
  output="$(pdnsutil list-zone "$zone" 2>/dev/null || true)"

  if [ -z "$output" ]; then
    out_error "dns zone not found or empty: $zone"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"zone":"%s","raw":"%s"}\n' \
      "$(json_escape "$zone")" \
      "$(json_escape "$output")"
  else
    printf '%s\n' "$output"
  fi
}

dns_record_list() {
  require_command pdnsutil

  local zone output

  zone="$(normalize_dns_zone "$(get_arg "--zone" "" "${ARGS[@]}")")"
  output="$(pdnsutil list-zone "$zone" 2>/dev/null || true)"

  if [ -z "$output" ]; then
    out_error "dns zone not found or empty: $zone"
  fi

  if [ "$JSON" -eq 1 ]; then
    printf '{"ok":true,"zone":"%s","records_raw":"%s"}\n' \
      "$(json_escape "$zone")" \
      "$(json_escape "$output")"
  else
    printf '%s\n' "$output"
  fi
}

case "$COMMAND" in
  php:list)
    php_list
  ;;

  php:default)
    php_default
  ;;

  user:create)
    user_create
  ;;

  user:delete)
    user_delete
  ;;

  user:sftp:only)
    user_sftp_only
  ;;

  user:sftp:on)
    user_sftp_only
  ;;

  user:sftp:off)
    user_sftp_off
  ;;

  site:list)
    site_list
  ;;

  site:create)
    site_create
  ;;

  site:add:php)
    site_create
  ;;

  site:delete)
    site_delete
  ;;

  site:php:switch)
    site_php_switch
  ;;

  site:local-domain:add)
    site_local_domain_add
  ;;

  system:domain:set)
    system_domain_set
  ;;

  system:domain:show)
    system_domain_show
  ;;

  user:system-domain:create)
    user_system_domain_create
  ;;

  domain:list)
    domain_list
  ;;

  domain:add)
    domain_add
  ;;

  domain:show)
    domain_show
  ;;

  domain:delete)
    domain_delete
  ;;

  site:domain:attach)
    site_domain_attach
  ;;

  site:domain:detach)
    site_domain_detach
  ;;

  site:domain:primary)
    site_domain_primary
  ;;

  domain:transfer)
    domain_transfer
  ;;

  site:transfer)
    site_transfer
  ;;

  site:ssl:issue)
    site_ssl_issue
  ;;
  
  site:ssl:wildcard)
    site_ssl_wildcard
  ;;

  site:ssl:local)
    site_ssl_local
  ;;
  
  db:list)
    db_list
  ;;

  db:create)
    db_create
  ;;

  db:delete)
    db_delete
  ;;

  db:user:create)
    db_user_create
  ;;

  db:user:delete)
    db_user_delete
  ;;
  
  backup:create)
    backup_create
  ;;

  backup:list)
    backup_list
  ;;

  backup:delete)
    backup_delete
  ;;

  backup:restore)
    backup_restore
  ;;
  
  logs:list)
    logs_list
  ;;

  logs:read)
    logs_read
  ;;

  logs:tail)
    logs_tail
  ;;
  
  system:status)
    system_status
  ;;

  services:status)
    services_status
  ;;

  disk:status)
    disk_status
  ;;

  memory:status)
    memory_status
  ;;

  cpu:status)
    cpu_status
  ;;

  ports:list)
    ports_list
  ;;
  
  about)
    about_info
  ;;
  
  mail:status)
    mail_status
  ;;

  mail:queue)
    mail_queue
  ;;

  mail:test)
    mail_test
  ;;

  mail:relay:set)
    mail_relay_set
  ;;

  mail:relay:disable)
    mail_relay_disable
  ;;
  
  cron:status)
    cron_status
  ;;

  cron:list)
    cron_list
  ;;

  cron:create)
    cron_create
  ;;

  cron:delete)
    cron_delete
  ;;

  license:show)
    license_show
  ;;

  db:root:show)
    db_root_show
  ;;
  
  packages:list)
    packages_list
  ;;
  
  package:show)
    package_show
  ;;
  
    antivirus:status)
    antivirus_status
  ;;

  antivirus:socket)
    antivirus_socket
  ;;

  antivirus:scan)
    antivirus_scan
  ;;

  antivirus:update)
    antivirus_update
  ;;

  firewall:status)
    firewall_status
  ;;

  firewall:enable)
    firewall_enable
  ;;

  firewall:disable)
    firewall_disable
  ;;

  firewall:reload)
    firewall_reload
  ;;

  firewall:allow)
    firewall_allow
  ;;

  firewall:delete)
    firewall_delete
  ;;

  fail2ban:status)
    fail2ban_status
  ;;

  fail2ban:jails)
    fail2ban_jails
  ;;

  fail2ban:jail)
    fail2ban_jail
  ;;

  fail2ban:ban)
    fail2ban_ban
  ;;

  fail2ban:unban)
    fail2ban_unban
  ;;
  
  limits:status)
    limits_status
  ;;

  limits:set)
    limits_set
  ;;

  limits:usage)
    limits_usage
  ;;

  limits:check)
    limits_check
  ;;
  
  queue:status)
    queue_status
  ;;

  queue:restart)
    queue_restart
  ;;

  queue:logs)
    queue_logs
  ;;
  
  app:list)
    app_list
  ;;

  app:versions)
    app_versions
  ;;

  app:defaults)
    app_defaults
  ;;
  
  app:service:create)
    app_service_create
  ;;

  app:service:start)
    app_service_start
  ;;

  app:service:stop)
    app_service_stop
  ;;

  app:service:restart)
    app_service_restart
  ;;

  app:service:status)
    app_service_status
  ;;

  app:service:logs)
    app_service_logs
  ;;
  
  app:port:allocate)
    app_port_allocate
  ;;

  app:render:nginx)
    app_render_nginx
  ;;

  app:render:service)
    app_render_service
  ;;
  
  ssl:list)
    ssl_list
  ;;

  ssl:status)
    ssl_status_domain
  ;;

  ssl:renew)
    ssl_renew
  ;;

  ssl:local:create)
    ssl_local_create
  ;;

  ssl:local:renew)
    ssl_local_renew
  ;;

  ssl:local:delete)
    ssl_local_delete
  ;;

  ssl:logs)
    ssl_logs
  ;;
  
  node:list)
    node_list
  ;;
  
  powerdns:configure)
    powerdns_configure
  ;;

  powerdns:status)
    powerdns_status
  ;;

  dns:zone:list)
    dns_zone_list
  ;;
  
  dns:zone:show)
    dns_zone_show
  ;;

  dns:zone:create)
    dns_zone_create
  ;;

  dns:zone:delete)
    dns_zone_delete
  ;;

  dns:record:add)
    dns_record_add
  ;;

  dns:record:replace)
    dns_record_replace
  ;;

  dns:record:delete)
    dns_record_delete
  ;;

  dns:record:list)
    dns_record_list
  ;;

  node:install)
    node_install
  ;;

  node:default)
    node_default
  ;;

  node:remove)
    node_remove
  ;;

  app:install)
    app_install
  ;;

  bun:install)
    bun_install
  ;;

  service:reload)
    service_reload
  ;;

  cache:clear)
    cache_clear
  ;;

  nginx:test)
    nginx_test
  ;;

  nginx:reload)
    nginx_reload
  ;;

  system:permissions:reset)
    permissions_reset
  ;;

  ""|help|--help|-h)
    usage
  ;;

  *)
    out_error "unknown command: $COMMAND"
  ;;
esac
