#!/bin/sh
#
# Server Init Script
# - Supports: Debian 11/12/13, Ubuntu, CentOS 7/8/9 (incl. Stream), RHEL, Alpine
# - Interactive & non-interactive (-y) modes
#

SCRIPT_NAME="server-init.sh"
SCRIPT_VERSION="1.0.0"

PUBKEY='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIECbc52mNhU2e4cMQgy0yevhTGYeE+iqDyUci2IsNLsY Generated By Termius'

NZ_SERVER="152.53.164.193:8008"
NZ_TLS="false"
NZ_CLIENT_SECRET="lLeMIi5ppyLOYSpnOV4jq7Vu6LLtptOd"

DOCKER_REGISTRY_MIRROR="https://docker-pull.ygxz.in"

AUTO_YES=0
DRY_RUN=0
SKIP_MODULES=""
ONLY_MODULES=""
SUPPRESS_SUMMARY=0

FAILED_MODULES=""
START_TS="$(date +%s 2>/dev/null || echo 0)"
TEMP_FILES=""

COLOR_RESET="$(printf '\033[0m')"
COLOR_RED="$(printf '\033[31m')"
COLOR_GREEN="$(printf '\033[32m')"
COLOR_YELLOW="$(printf '\033[33m')"
COLOR_BLUE="$(printf '\033[34m')"

OS_ID=""
OS_NAME=""
OS_PRETTY_NAME=""
OS_LIKE=""
OS_VERSION_ID=""
OS_VERSION_MAJOR=""
OS_VERSION_CODENAME=""
ARCH=""
KERNEL=""
PKG_MGR=""
INIT_SYSTEM=""
IS_CENTOS_STREAM=0

PUBLIC_IP=""
COUNTRY_CODE=""
IS_CN=0

ts() { date "+%Y-%m-%d %H:%M:%S" 2>/dev/null || date; }

log() {
  level="$1"
  color="$2"
  shift 2
  printf "%s %s[%s]%s %s\n" "$(ts)" "$color" "$level" "$COLOR_RESET" "$*" >&2
}

log_info() { log "INFO" "$COLOR_BLUE" "$@"; }
log_ok() { log " OK " "$COLOR_GREEN" "$@"; }
log_warn() { log "WARN" "$COLOR_YELLOW" "$@"; }
log_err() { log "ERR " "$COLOR_RED" "$@"; }

die() {
  log_err "$@"
  exit 1
}

have_cmd() { command -v "$1" >/dev/null 2>&1; }

register_temp() {
  TEMP_FILES="$TEMP_FILES $1"
}

cleanup_temp() {
  for f in $TEMP_FILES; do
    rm -f "$f" 2>/dev/null || true
  done
}

make_temp() {
  prefix="${1:-server-init}"
  if have_cmd mktemp; then
    tmp="$(mktemp "/tmp/${prefix}.XXXXXX")"
  else
    tmp="/tmp/${prefix}.$$"
  fi
  register_temp "$tmp"
  echo "$tmp"
}

trim() {
  # trim leading/trailing whitespace
  # shellcheck disable=SC2001
  echo "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
}

list_contains() {
  list="$1"
  item="$2"
  case " $list " in
    *" $item "*) return 0 ;;
    *) return 1 ;;
  esac
}

confirm() {
  prompt="$1"
  if [ "$AUTO_YES" -eq 1 ]; then
    return 0
  fi
  printf "%s [y/N]: " "$prompt" >&2
  read -r ans || return 1
  ans="$(trim "$ans")"
  case "$ans" in
    y|Y|yes|YES) return 0 ;;
    *) return 1 ;;
  esac
}

backup_file() {
  path="$1"
  [ -e "$path" ] || return 0
  ts_suffix="$(date "+%Y%m%d-%H%M%S" 2>/dev/null || echo "backup")"
  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] would backup $path to ${path}.bak.${ts_suffix}"
    return 0
  fi
  cp -a "$path" "${path}.bak.${ts_suffix}" 2>/dev/null || return 1
}

run_cmd() {
  desc="$1"
  shift
  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] $desc: $*"
    return 0
  fi
  "$@"
  rc=$?
  if [ $rc -ne 0 ]; then
    log_err "$desc failed (exit=$rc)"
  fi
  return $rc
}

retry() {
  max="$1"
  shift
  n=1
  while [ $n -le "$max" ]; do
    "$@" && return 0
    n=$((n + 1))
    sleep 1
  done
  return 1
}

http_get() {
  url="$1"
  if have_cmd curl; then
    retry 3 curl -fsSL --max-time 12 "$url"
    return $?
  fi
  if have_cmd wget; then
    retry 3 wget -qO- --timeout=12 "$url"
    return $?
  fi
  return 127
}

require_root() {
  if [ "$(id -u)" -eq 0 ]; then
    return 0
  fi
  if have_cmd sudo; then
    log_info "Re-running as root via sudo..."
    exec sudo -E sh "$0" "$@"
  fi
  die "Please run as root (or install sudo)."
}

detect_system() {
  if [ -r /etc/os-release ]; then
    # shellcheck disable=SC1091
    . /etc/os-release
    OS_ID="${ID:-}"
    OS_NAME="${NAME:-}"
    OS_PRETTY_NAME="${PRETTY_NAME:-$OS_NAME}"
    OS_LIKE="${ID_LIKE:-}"
    OS_VERSION_ID="${VERSION_ID:-}"
    OS_VERSION_CODENAME="${VERSION_CODENAME:-}"
  fi

  ARCH="$(uname -m 2>/dev/null || echo unknown)"
  KERNEL="$(uname -r 2>/dev/null || echo unknown)"
  OS_VERSION_MAJOR="$(printf '%s' "$OS_VERSION_ID" | cut -d. -f1)"

  if have_cmd apt-get; then
    PKG_MGR="apt"
  elif have_cmd dnf; then
    PKG_MGR="dnf"
  elif have_cmd yum; then
    PKG_MGR="yum"
  elif have_cmd apk; then
    PKG_MGR="apk"
  else
    PKG_MGR=""
  fi

  if have_cmd systemctl && [ -d /run/systemd/system ]; then
    INIT_SYSTEM="systemd"
  elif have_cmd rc-service; then
    INIT_SYSTEM="openrc"
  else
    INIT_SYSTEM="unknown"
  fi

  case "$OS_PRETTY_NAME $OS_NAME" in
    *"CentOS Stream"*) IS_CENTOS_STREAM=1 ;;
    *) IS_CENTOS_STREAM=0 ;;
  esac

  log_info "Detected OS: ${OS_PRETTY_NAME:-unknown} (id=${OS_ID:-unknown}, version=${OS_VERSION_ID:-unknown}, arch=$ARCH, init=$INIT_SYSTEM)"
}

validate_supported_os() {
  case "$OS_ID" in
    debian|ubuntu|centos|rhel|alpine) return 0 ;;
    *)
      log_warn "Unsupported or unknown OS id: ${OS_ID:-unknown}. Continuing in best-effort mode."
      return 0
      ;;
  esac
}

detect_location() {
  log_info "Detecting server location (CN/non-CN)..."

  ip="$(http_get "https://api.ip.sb/ip" 2>/dev/null | head -n 1 | tr -d '\r' | tr -d '\n')"
  case "$ip" in
    ""|*" "*|*"/"*) ip="" ;;
  esac
  PUBLIC_IP="$ip"

  cc=""
  geo="$(http_get "https://api.ip.sb/geoip" 2>/dev/null || true)"
  if [ -n "$geo" ]; then
    cc="$(printf '%s' "$geo" | sed -n 's/.*"country_code"[[:space:]]*:[[:space:]]*"\([A-Z][A-Z]\)".*/\1/p' | head -n 1)"
  fi

  if [ -z "$cc" ]; then
    cc="$(http_get "https://ipinfo.io/country" 2>/dev/null | head -n 1 | tr -d '\r' | tr -d '\n' || true)"
  fi

  if [ -z "$cc" ]; then
    cc="$(http_get "http://ip-api.com/line/?fields=countryCode" 2>/dev/null | head -n 1 | tr -d '\r' | tr -d '\n' || true)"
  fi

  cc="$(trim "$cc")"
  COUNTRY_CODE="$cc"

  if [ "$COUNTRY_CODE" = "CN" ]; then
    IS_CN=1
    log_ok "Location detected: CN (public_ip=${PUBLIC_IP:-unknown})"
    return 0
  fi

  if [ -n "$COUNTRY_CODE" ]; then
    IS_CN=0
    log_ok "Location detected: ${COUNTRY_CODE} (public_ip=${PUBLIC_IP:-unknown})"
    return 0
  fi

  log_warn "Location detection unavailable (no downloader or network issue)."
  if [ "$AUTO_YES" -eq 1 ]; then
    IS_CN=0
    log_warn "Non-interactive mode: defaulting to non-CN (no mirror change)."
    return 0
  fi

  if confirm "Is this server located in Mainland China (CN)?"; then
    IS_CN=1
    COUNTRY_CODE="CN"
    log_ok "Location set by user: CN"
  else
    IS_CN=0
    log_ok "Location set by user: non-CN"
  fi
}

apt_write_debian_deb822_sources() {
  codename="$1"
  [ -n "$codename" ] || return 1
  file="/etc/apt/sources.list.d/debian.sources"

  backup_file "$file" || return 1

  if [ "$DRY_RUN" -eq 0 ]; then
    mkdir -p /etc/apt/sources.list.d 2>/dev/null || true
  fi

  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] write $file (deb822) for Debian $codename via Aliyun"
    return 0
  fi

  cat >"$file" <<EOF
Types: deb
URIs: https://mirrors.aliyun.com/debian/
Suites: ${codename} ${codename}-updates
Components: main contrib non-free non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg

Types: deb
URIs: https://mirrors.aliyun.com/debian-security/
Suites: ${codename}-security
Components: main contrib non-free non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
EOF
}

apt_patch_sources_list_hosts() {
  file="$1"
  [ -f "$file" ] || return 0
  backup_file "$file" || return 1
  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] patch $file hosts to Aliyun"
    return 0
  fi

  sed -i \
    -e 's|http://deb.debian.org/debian|https://mirrors.aliyun.com/debian|g' \
    -e 's|https://deb.debian.org/debian|https://mirrors.aliyun.com/debian|g' \
    -e 's|http://security.debian.org/debian-security|https://mirrors.aliyun.com/debian-security|g' \
    -e 's|https://security.debian.org/debian-security|https://mirrors.aliyun.com/debian-security|g' \
    -e 's|http://archive.ubuntu.com/ubuntu|https://mirrors.aliyun.com/ubuntu|g' \
    -e 's|https://archive.ubuntu.com/ubuntu|https://mirrors.aliyun.com/ubuntu|g' \
    -e 's|http://security.ubuntu.com/ubuntu|https://mirrors.aliyun.com/ubuntu|g' \
    -e 's|https://security.ubuntu.com/ubuntu|https://mirrors.aliyun.com/ubuntu|g' \
    "$file"
}

apt_patch_deb822_uris() {
  file="$1"
  [ -f "$file" ] || return 0
  backup_file "$file" || return 1
  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] patch $file URIs to Aliyun"
    return 0
  fi

  sed -i \
    -e 's|URIs:[[:space:]]*http://deb.debian.org/debian/?|URIs: https://mirrors.aliyun.com/debian/|g' \
    -e 's|URIs:[[:space:]]*https://deb.debian.org/debian/?|URIs: https://mirrors.aliyun.com/debian/|g' \
    -e 's|URIs:[[:space:]]*http://security.debian.org/debian-security/?|URIs: https://mirrors.aliyun.com/debian-security/|g' \
    -e 's|URIs:[[:space:]]*https://security.debian.org/debian-security/?|URIs: https://mirrors.aliyun.com/debian-security/|g' \
    -e 's|URIs:[[:space:]]*http://archive.ubuntu.com/ubuntu/?|URIs: https://mirrors.aliyun.com/ubuntu/|g' \
    -e 's|URIs:[[:space:]]*https://archive.ubuntu.com/ubuntu/?|URIs: https://mirrors.aliyun.com/ubuntu/|g' \
    -e 's|URIs:[[:space:]]*http://security.ubuntu.com/ubuntu/?|URIs: https://mirrors.aliyun.com/ubuntu/|g' \
    -e 's|URIs:[[:space:]]*https://security.ubuntu.com/ubuntu/?|URIs: https://mirrors.aliyun.com/ubuntu/|g' \
    "$file"
}

configure_mirrors() {
  if [ "$IS_CN" -ne 1 ]; then
    log_info "Non-CN server: skip mirror configuration."
    return 0
  fi

  log_info "Configuring package mirrors for CN (Aliyun)..."

  case "$OS_ID" in
    debian)
      codename="$OS_VERSION_CODENAME"
      if [ -z "$codename" ] && have_cmd lsb_release; then
        codename="$(lsb_release -cs 2>/dev/null || true)"
      fi
      if [ -z "$codename" ]; then
        log_warn "Debian codename not found; patching existing sources if possible."
        apt_patch_sources_list_hosts /etc/apt/sources.list || true
        return 0
      fi

      if [ "${OS_VERSION_MAJOR:-0}" -ge 13 ] || [ "$codename" = "trixie" ]; then
        apt_write_debian_deb822_sources "$codename" || return 1
      else
        if [ -f /etc/apt/sources.list.d/debian.sources ]; then
          apt_write_debian_deb822_sources "$codename" || return 1
        else
          apt_patch_sources_list_hosts /etc/apt/sources.list || return 1
        fi
      fi
      ;;
    ubuntu)
      if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then
        apt_patch_deb822_uris /etc/apt/sources.list.d/ubuntu.sources || return 1
      fi
      apt_patch_sources_list_hosts /etc/apt/sources.list || true
      ;;
    centos)
      for f in /etc/yum.repos.d/*.repo; do
        [ -e "$f" ] || continue
        backup_file "$f" || return 1
        if [ "$DRY_RUN" -eq 1 ]; then
          log_info "[dry-run] patch $f for Aliyun mirror"
          continue
        fi

        sed -i \
          -e 's|^mirrorlist=|#mirrorlist=|g' \
          -e 's|^#mirrorlist=|#mirrorlist=|g' \
          -e 's|^metalink=|#metalink=|g' \
          -e 's|^#metalink=|#metalink=|g' \
          "$f"

        if [ "$IS_CENTOS_STREAM" -eq 1 ]; then
          sed -i \
            -e 's|^#baseurl=http://mirror.stream.centos.org|baseurl=https://mirrors.aliyun.com/centos-stream|g' \
            -e 's|^#baseurl=https://mirror.stream.centos.org|baseurl=https://mirrors.aliyun.com/centos-stream|g' \
            -e 's|^baseurl=http://mirror.stream.centos.org|baseurl=https://mirrors.aliyun.com/centos-stream|g' \
            -e 's|^baseurl=https://mirror.stream.centos.org|baseurl=https://mirrors.aliyun.com/centos-stream|g' \
            "$f"
        else
          sed -i \
            -e 's|^#baseurl=http://mirror.centos.org|baseurl=https://mirrors.aliyun.com|g' \
            -e 's|^#baseurl=https://mirror.centos.org|baseurl=https://mirrors.aliyun.com|g' \
            -e 's|^baseurl=http://mirror.centos.org|baseurl=https://mirrors.aliyun.com|g' \
            -e 's|^baseurl=https://mirror.centos.org|baseurl=https://mirrors.aliyun.com|g' \
            -e 's|^#baseurl=http://vault.centos.org|baseurl=https://mirrors.aliyun.com|g' \
            -e 's|^#baseurl=https://vault.centos.org|baseurl=https://mirrors.aliyun.com|g' \
            -e 's|^baseurl=http://vault.centos.org|baseurl=https://mirrors.aliyun.com|g' \
            -e 's|^baseurl=https://vault.centos.org|baseurl=https://mirrors.aliyun.com|g' \
            "$f"
        fi
      done
      ;;
    rhel)
      log_info "RHEL detected: keep default repositories (subscription-managed)."
      ;;
    alpine)
      if [ ! -r /etc/alpine-release ]; then
        log_warn "Alpine release file not found; skipping mirror config."
        return 0
      fi
      ver="$(cut -d. -f1-2 /etc/alpine-release 2>/dev/null || true)"
      if [ -z "$ver" ]; then
        log_warn "Unable to detect Alpine version; skipping mirror config."
        return 0
      fi
      backup_file /etc/apk/repositories || return 1
      if [ "$DRY_RUN" -eq 1 ]; then
        log_info "[dry-run] write /etc/apk/repositories for Alpine v$ver via Aliyun"
        return 0
      fi
      cat >/etc/apk/repositories <<EOF
https://mirrors.aliyun.com/alpine/v${ver}/main
https://mirrors.aliyun.com/alpine/v${ver}/community
EOF
      ;;
    *)
      log_warn "Mirror config: unsupported OS id=$OS_ID"
      ;;
  esac
}

pkg_update() {
  case "$PKG_MGR" in
    apt) run_cmd "apt-get update" sh -c "DEBIAN_FRONTEND=noninteractive apt-get update" ;;
    dnf) run_cmd "dnf makecache" dnf -y makecache ;;
    yum) run_cmd "yum makecache" yum -y makecache ;;
    apk) run_cmd "apk update" apk update ;;
    *)
      log_warn "No supported package manager found for update."
      return 1
      ;;
  esac
}

pkg_install() {
  if [ $# -eq 0 ]; then
    return 0
  fi
  case "$PKG_MGR" in
    apt)
      run_cmd "apt-get install" sh -c "DEBIAN_FRONTEND=noninteractive apt-get install -y $*"
      ;;
    dnf)
      run_cmd "dnf install" dnf -y install "$@"
      ;;
    yum)
      run_cmd "yum install" yum -y install "$@"
      ;;
    apk)
      run_cmd "apk add" apk add --no-cache "$@"
      ;;
    *)
      log_warn "No supported package manager found for install."
      return 1
      ;;
  esac
}

install_packages() {
  log_info "Installing base packages..."
  pkg_update || true

  case "$PKG_MGR" in
    apt)
      pkg_install sudo curl wget unzip git vim htop tmux net-tools dnsutils ca-certificates gnupg lsb-release kmod || return 1
      ;;
    yum|dnf)
      # Try to cover common tooling; names differ slightly across versions.
      pkg_install sudo curl wget unzip git vim htop tmux net-tools bind-utils ca-certificates gnupg2 kmod || true
      pkg_install vim-enhanced || true
      ;;
    apk)
      pkg_install sudo curl wget unzip git vim htop tmux bind-tools ca-certificates gnupg kmod util-linux || true
      ;;
    *)
      log_warn "install_packages: unsupported package manager."
      return 1
      ;;
  esac

  log_ok "Base packages installed (best-effort)."
}

get_user_home() {
  u="$1"
  if have_cmd getent; then
    getent passwd "$u" 2>/dev/null | cut -d: -f6
    return
  fi
  awk -F: -v u="$u" '$1==u{print $6; exit}' /etc/passwd 2>/dev/null || true
}

ensure_user_pubkey() {
  user="$1"
  home="$2"
  [ -n "$home" ] || return 1

  ssh_dir="${home}/.ssh"
  auth_keys="${ssh_dir}/authorized_keys"

  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] add SSH pubkey to ${auth_keys} (user=$user)"
    return 0
  fi

  old_umask="$(umask)"
  umask 077
  mkdir -p "$ssh_dir" || { umask "$old_umask"; return 1; }
  touch "$auth_keys" || { umask "$old_umask"; return 1; }
  chmod 700 "$ssh_dir" || true
  chmod 600 "$auth_keys" || true

  if grep -Fqx "$PUBKEY" "$auth_keys" 2>/dev/null; then
    log_info "SSH pubkey already exists for user=$user"
  else
    printf '%s\n' "$PUBKEY" >>"$auth_keys" || { umask "$old_umask"; return 1; }
    log_ok "SSH pubkey appended for user=$user"
  fi

  if have_cmd chown; then
    if id "$user" >/dev/null 2>&1; then
      chown "$user":"$user" "$ssh_dir" "$auth_keys" 2>/dev/null || chown "$user" "$ssh_dir" "$auth_keys" 2>/dev/null || true
    fi
  fi

  umask "$old_umask"
}

add_ssh_pubkey() {
  log_info "Adding SSH public key (fixed)..."
  ensure_user_pubkey "root" "/root" || return 1
  if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
    home="$(get_user_home "$SUDO_USER")"
    if [ -n "$home" ]; then
      ensure_user_pubkey "$SUDO_USER" "$home" || true
    fi
  fi
}

run_as_user() {
  user="$1"
  shift
  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] run as user=$user: $*"
    return 0
  fi
  if [ "$user" = "root" ]; then
    sh -c "$*"
    return $?
  fi
  if have_cmd sudo; then
    sudo -u "$user" -H sh -c "$*"
    return $?
  fi
  if have_cmd su; then
    su - "$user" -c "$*"
    return $?
  fi
  return 1
}

ensure_zsh_plugins() {
  user="$1"
  home="$2"
  zsh_custom="${home}/.oh-my-zsh/custom"
  plugins_dir="${zsh_custom}/plugins"

  run_as_user "$user" "mkdir -p '$plugins_dir'" || return 1

  if [ ! -d "${plugins_dir}/zsh-autosuggestions" ]; then
    run_as_user "$user" "git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions '${plugins_dir}/zsh-autosuggestions'" || true
  fi
  if [ ! -d "${plugins_dir}/zsh-syntax-highlighting" ]; then
    run_as_user "$user" "git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting '${plugins_dir}/zsh-syntax-highlighting'" || true
  fi

  zshrc="${home}/.zshrc"
  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] ensure plugins in $zshrc"
    return 0
  fi

  if [ ! -f "$zshrc" ]; then
    run_as_user "$user" "touch '$zshrc'" || true
  fi

  if grep -q '^plugins=' "$zshrc" 2>/dev/null; then
    if ! grep -q 'zsh-autosuggestions' "$zshrc" 2>/dev/null; then
      sed -i 's/^plugins=(\([^)]*\))/plugins=(\1 zsh-autosuggestions)/' "$zshrc" || true
    fi
    if ! grep -q 'zsh-syntax-highlighting' "$zshrc" 2>/dev/null; then
      sed -i 's/^plugins=(\([^)]*\))/plugins=(\1 zsh-syntax-highlighting)/' "$zshrc" || true
    fi
  else
    printf '\nplugins=(git zsh-autosuggestions zsh-syntax-highlighting)\n' >>"$zshrc" || true
  fi
}

configure_zsh_for_user() {
  user="$1"
  home="$2"
  [ -n "$home" ] || return 0

  log_info "Configuring zsh for user=$user"

  if [ ! -d "${home}/.oh-my-zsh" ]; then
    if ! have_cmd curl && ! have_cmd wget; then
      log_warn "curl/wget not found; cannot install oh-my-zsh for user=$user"
    else
      installer="$(make_temp ohmyzsh-install)"
      if [ "$DRY_RUN" -eq 1 ]; then
        log_info "[dry-run] download oh-my-zsh installer to $installer"
      else
        http_get "https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh" >"$installer" || true
        chmod +x "$installer" 2>/dev/null || true
      fi
      run_as_user "$user" "RUNZSH=no CHSH=no KEEP_ZSHRC=yes sh '$installer'" || true
    fi
  else
    log_info "oh-my-zsh already installed for user=$user"
  fi

  ensure_zsh_plugins "$user" "$home" || true

  zsh_path="$(command -v zsh 2>/dev/null || true)"
  if [ -n "$zsh_path" ] && have_cmd chsh; then
    if [ "$DRY_RUN" -eq 1 ]; then
      log_info "[dry-run] chsh -s '$zsh_path' '$user'"
    else
      chsh -s "$zsh_path" "$user" 2>/dev/null || true
    fi
  fi
}

configure_zsh() {
  log_info "Installing zsh and configuring oh-my-zsh..."
  pkg_install zsh || true
  have_cmd zsh || { log_warn "zsh not available; skip zsh setup."; return 0; }

  configure_zsh_for_user "root" "/root"
  if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
    home="$(get_user_home "$SUDO_USER")"
    [ -n "$home" ] && configure_zsh_for_user "$SUDO_USER" "$home"
  fi
}

write_json_with_python() {
  path="$1"
  mirror="$2"
  if have_cmd python3; then
    python3 - "$path" "$mirror" <<'PY'
import json, sys
path, mirror = sys.argv[1], sys.argv[2]
try:
    with open(path, "r", encoding="utf-8") as f:
        data = json.load(f)
except Exception:
    data = {}
mirrors = data.get("registry-mirrors")
if not isinstance(mirrors, list):
    mirrors = []
if mirror not in mirrors:
    mirrors.append(mirror)
data["registry-mirrors"] = mirrors
with open(path, "w", encoding="utf-8") as f:
    json.dump(data, f, indent=2, sort_keys=True)
    f.write("\n")
PY
    return $?
  fi
  if have_cmd python; then
    python - "$path" "$mirror" <<'PY'
import json, sys
path, mirror = sys.argv[1], sys.argv[2]
try:
    with open(path, "r") as f:
        data = json.load(f)
except Exception:
    data = {}
mirrors = data.get("registry-mirrors")
if not isinstance(mirrors, list):
    mirrors = []
if mirror not in mirrors:
    mirrors.append(mirror)
data["registry-mirrors"] = mirrors
with open(path, "w") as f:
    json.dump(data, f, indent=2, sort_keys=True)
    f.write("\n")
PY
    return $?
  fi
  return 127
}

configure_docker_mirror() {
  daemon_json="/etc/docker/daemon.json"

  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] ensure Docker registry mirror in $daemon_json: $DOCKER_REGISTRY_MIRROR"
    return 0
  fi

  mkdir -p /etc/docker 2>/dev/null || true

  if [ -f "$daemon_json" ]; then
    backup_file "$daemon_json" || true
  fi

  if [ -f "$daemon_json" ]; then
    if write_json_with_python "$daemon_json" "$DOCKER_REGISTRY_MIRROR" >/dev/null 2>&1; then
      log_ok "Docker registry mirror merged into existing $daemon_json"
      return 0
    else
      log_warn "Cannot merge registry mirror into $daemon_json (no python or invalid JSON)"
      log_warn "Please manually add: \"registry-mirrors\": [\"$DOCKER_REGISTRY_MIRROR\"]"
      return 0
    fi
  fi

  cat >"$daemon_json" <<EOF
{
  "registry-mirrors": [
    "${DOCKER_REGISTRY_MIRROR}"
  ]
}
EOF
  log_ok "Docker registry mirror configured in $daemon_json"
}

start_enable_service() {
  svc="$1"
  case "$INIT_SYSTEM" in
    systemd)
      run_cmd "systemctl enable --now $svc" systemctl enable --now "$svc"
      ;;
    openrc)
      if have_cmd rc-update; then
        run_cmd "rc-update add $svc" rc-update add "$svc" default || true
      fi
      run_cmd "rc-service start $svc" rc-service "$svc" start || true
      ;;
    *)
      if have_cmd service; then
        run_cmd "service start $svc" service "$svc" start || true
      fi
      ;;
  esac
}

restart_service() {
  svc="$1"
  case "$INIT_SYSTEM" in
    systemd)
      run_cmd "systemctl restart $svc" systemctl restart "$svc"
      ;;
    openrc)
      run_cmd "rc-service restart $svc" rc-service "$svc" restart || true
      ;;
    *)
      if have_cmd service; then
        run_cmd "service restart $svc" service "$svc" restart || true
      fi
      ;;
  esac
}

install_docker() {
  log_info "Installing Docker..."

  if have_cmd docker; then
    log_info "Docker already installed; configuring registry mirror and enabling service."
    configure_docker_mirror || true
    start_enable_service docker || true
    return 0
  fi

  case "$OS_ID" in
    alpine)
      pkg_install docker docker-cli docker-compose || pkg_install docker || true
      ;;
    *)
      tmp="$(make_temp get-docker)"
      if [ "$DRY_RUN" -eq 1 ]; then
        log_info "[dry-run] download https://get.docker.com to $tmp and run"
      else
        http_get "https://get.docker.com" >"$tmp" || return 1
        chmod +x "$tmp" || true
      fi
      if [ "$IS_CN" -eq 1 ]; then
        run_cmd "install docker (mirror Aliyun)" sh "$tmp" --mirror Aliyun || return 1
      else
        run_cmd "install docker" sh "$tmp" || return 1
      fi
      ;;
  esac

  configure_docker_mirror || true
  start_enable_service docker || true
  restart_service docker || true
  log_ok "Docker installation completed (best-effort)."
}

sshd_set() {
  key="$1"
  val="$2"
  file="/etc/ssh/sshd_config"
  [ -f "$file" ] || return 1

  if grep -Eq "^[#[:space:]]*${key}[[:space:]]+" "$file"; then
    sed -i "s|^[#[:space:]]*${key}[[:space:]].*|${key} ${val}|" "$file" || return 1
  else
    printf '\n%s %s\n' "$key" "$val" >>"$file" || return 1
  fi
}

restart_sshd() {
  if [ "$INIT_SYSTEM" = "systemd" ]; then
    systemctl is-active sshd >/dev/null 2>&1 && restart_service sshd && return 0
    systemctl is-active ssh >/dev/null 2>&1 && restart_service ssh && return 0
    restart_service sshd || restart_service ssh || true
    return 0
  fi

  if [ "$INIT_SYSTEM" = "openrc" ]; then
    restart_service sshd || restart_service ssh || true
    return 0
  fi

  if have_cmd service; then
    service sshd restart >/dev/null 2>&1 || service ssh restart >/dev/null 2>&1 || true
  fi
}

harden_ssh() {
  log_info "Hardening SSH configuration..."
  file="/etc/ssh/sshd_config"
  [ -f "$file" ] || { log_warn "sshd_config not found; skip."; return 0; }

  backup_file "$file" || return 1

  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] update $file: PasswordAuthentication no; PubkeyAuthentication yes; PermitRootLogin prohibit-password"
    return 0
  fi

  sshd_set "PasswordAuthentication" "no" || return 1
  sshd_set "PubkeyAuthentication" "yes" || return 1
  sshd_set "PermitRootLogin" "prohibit-password" || return 1

  if have_cmd sshd; then
    if ! sshd -t 2>/dev/null; then
      log_err "sshd config validation failed (sshd -t). Please check $file"
      return 1
    fi
  fi

  restart_sshd || true
  log_ok "SSH hardened."
}

install_fail2ban() {
  log_info "Installing and configuring fail2ban..."

  case "$PKG_MGR" in
    apt)
      pkg_install fail2ban || return 1
      ;;
    yum|dnf)
      # EPEL is commonly required.
      pkg_install epel-release || true
      pkg_install fail2ban || true
      ;;
    apk)
      pkg_install fail2ban || true
      ;;
    *)
      log_warn "fail2ban: unsupported package manager."
      return 1
      ;;
  esac

  jail="/etc/fail2ban/jail.local"
  backup_file "$jail" || true

  logpath=""
  if [ -f /var/log/auth.log ]; then
    logpath="/var/log/auth.log"
  elif [ -f /var/log/secure ]; then
    logpath="/var/log/secure"
  elif [ -f /var/log/messages ]; then
    logpath="/var/log/messages"
  fi

  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] write $jail (logpath=${logpath:-auto})"
  else
    mkdir -p /etc/fail2ban 2>/dev/null || true
    {
      echo "[DEFAULT]"
      echo "bantime = 3600"
      echo "findtime = 600"
      echo "maxretry = 5"
      echo ""
      echo "[sshd]"
      echo "enabled = true"
      echo "port = ssh"
      echo "filter = sshd"
      if [ -n "$logpath" ]; then
        echo "logpath = $logpath"
      fi
    } >"$jail"
  fi

  start_enable_service fail2ban || true
  restart_service fail2ban || true
  log_ok "fail2ban configured (best-effort)."
}

disable_zswap_if_present() {
  if [ -w /sys/module/zswap/parameters/enabled ]; then
    if [ "$DRY_RUN" -eq 1 ]; then
      log_info "[dry-run] disable zswap (/sys/module/zswap/parameters/enabled=0)"
      return 0
    fi
    echo 0 >/sys/module/zswap/parameters/enabled 2>/dev/null || true
  fi
}

calc_ram_mb() {
  awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null
}

calc_zram_percent_and_swap() {
  ram_mb="$1"
  zram_percent=50
  swap_gb=0

  if [ "$ram_mb" -le 2048 ]; then
    zram_percent=100
    swap_gb=2
  elif [ "$ram_mb" -le 4096 ]; then
    zram_percent=75
    swap_gb=2
  elif [ "$ram_mb" -le 8192 ]; then
    zram_percent=50
    swap_gb=4
  else
    zram_percent=25
    swap_gb=0
  fi

  echo "$zram_percent $swap_gb"
}

create_swapfile() {
  swap_gb="$1"
  [ "$swap_gb" -gt 0 ] || return 0

  if grep -q '^/swapfile ' /etc/fstab 2>/dev/null; then
    log_info "swapfile entry exists in /etc/fstab"
  fi

  if grep -q '^/swapfile' /proc/swaps 2>/dev/null; then
    log_info "swapfile already active."
    return 0
  fi

  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] create /swapfile (${swap_gb}G) and enable"
    return 0
  fi

  if [ ! -f /swapfile ]; then
    if have_cmd fallocate; then
      fallocate -l "${swap_gb}G" /swapfile || return 1
    else
      dd if=/dev/zero of=/swapfile bs=1M count=$((swap_gb * 1024)) || return 1
    fi
    chmod 600 /swapfile || true
    mkswap /swapfile >/dev/null 2>&1 || return 1
  fi

  swapon /swapfile || return 1

  if ! grep -q '^/swapfile ' /etc/fstab 2>/dev/null; then
    echo '/swapfile none swap sw 0 0' >>/etc/fstab || true
  fi
  log_ok "swapfile enabled: /swapfile (${swap_gb}G)"
}

install_manual_zram_service_systemd() {
  zram_percent="$1"
  setup="/usr/local/sbin/zram-swap.sh"
  unit="/etc/systemd/system/zram-swap.service"

  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] install $setup and $unit (percent=$zram_percent)"
    return 0
  fi

  mkdir -p /usr/local/sbin 2>/dev/null || true
  cat >"$setup" <<EOF
#!/bin/sh
set -eu

PERCENT=${zram_percent}
MEM_KB=\$(awk '/MemTotal/ {print \$2}' /proc/meminfo)
SIZE_BYTES=\$(( MEM_KB * 1024 * PERCENT / 100 ))

modprobe zram num_devices=1 2>/dev/null || modprobe zram 2>/dev/null || true

if [ -e /dev/zram0 ]; then
  swapoff /dev/zram0 2>/dev/null || true
  echo 1 >/sys/block/zram0/reset 2>/dev/null || true
fi

if [ -f /sys/block/zram0/comp_algorithm ]; then
  if grep -qw zstd /sys/block/zram0/comp_algorithm; then
    echo zstd >/sys/block/zram0/comp_algorithm 2>/dev/null || true
  fi
fi

echo "\${SIZE_BYTES}" >/sys/block/zram0/disksize
mkswap /dev/zram0 >/dev/null 2>&1
swapon -p 100 /dev/zram0
EOF
  chmod +x "$setup" || true

  cat >"$unit" <<EOF
[Unit]
Description=ZRAM swap (managed by ${SCRIPT_NAME})
After=multi-user.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=${setup}
ExecStop=/bin/sh -c 'swapoff /dev/zram0 2>/dev/null || true'

[Install]
WantedBy=multi-user.target
EOF

  systemctl daemon-reload || true
  systemctl enable --now zram-swap.service || true
}

install_manual_zram_service_openrc() {
  zram_percent="$1"
  setup="/usr/local/sbin/zram-swap.sh"
  initd="/etc/init.d/zram-swap"

  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] install $setup and $initd (percent=$zram_percent)"
    return 0
  fi

  mkdir -p /usr/local/sbin 2>/dev/null || true
  cat >"$setup" <<EOF
#!/bin/sh
set -eu

PERCENT=${zram_percent}
MEM_KB=\$(awk '/MemTotal/ {print \$2}' /proc/meminfo)
SIZE_BYTES=\$(( MEM_KB * 1024 * PERCENT / 100 ))

modprobe zram num_devices=1 2>/dev/null || modprobe zram 2>/dev/null || true

if [ -e /dev/zram0 ]; then
  swapoff /dev/zram0 2>/dev/null || true
  echo 1 >/sys/block/zram0/reset 2>/dev/null || true
fi

if [ -f /sys/block/zram0/comp_algorithm ]; then
  if grep -qw zstd /sys/block/zram0/comp_algorithm; then
    echo zstd >/sys/block/zram0/comp_algorithm 2>/dev/null || true
  fi
fi

echo "\${SIZE_BYTES}" >/sys/block/zram0/disksize
mkswap /dev/zram0 >/dev/null 2>&1
swapon -p 100 /dev/zram0
EOF
  chmod +x "$setup" || true

  cat >"$initd" <<EOF
#!/sbin/openrc-run

description="ZRAM swap (managed by ${SCRIPT_NAME})"

start() {
  ebegin "Setting up zram swap"
  ${setup}
  eend \$?
}

stop() {
  ebegin "Stopping zram swap"
  swapoff /dev/zram0 2>/dev/null || true
  eend 0
}
EOF
  chmod +x "$initd" || true

  rc-update add zram-swap default 2>/dev/null || true
  rc-service zram-swap start 2>/dev/null || true
}

configure_zram_swap() {
  log_info "Configuring zram and swap..."
  disable_zswap_if_present || true

  ram_mb="$(calc_ram_mb)"
  if [ -z "$ram_mb" ]; then
    log_warn "Unable to detect RAM size; skip zram/swap."
    return 0
  fi

  set -- $(calc_zram_percent_and_swap "$ram_mb")
  zram_percent="$1"
  swap_gb="$2"

  log_info "RAM=${ram_mb}MB -> zram=${zram_percent}% , swapfile=${swap_gb}G"

  case "$PKG_MGR" in
    apt)
      pkg_install zram-tools || true
      cfg="/etc/default/zramswap"
      backup_file "$cfg" || true
      if [ "$DRY_RUN" -eq 1 ]; then
        log_info "[dry-run] write $cfg (ALGO=zstd, PERCENT=$zram_percent)"
      else
        mkdir -p /etc/default 2>/dev/null || true
        {
          echo "ALGO=zstd"
          echo "PERCENT=${zram_percent}"
          echo "PRIORITY=100"
        } >"$cfg"
      fi
      start_enable_service zramswap || true
      restart_service zramswap || true
      ;;
    yum|dnf)
      if [ "$INIT_SYSTEM" = "systemd" ]; then
        install_manual_zram_service_systemd "$zram_percent" || true
      fi
      ;;
    apk)
      if [ "$INIT_SYSTEM" = "openrc" ]; then
        install_manual_zram_service_openrc "$zram_percent" || true
      fi
      ;;
    *)
      log_warn "zram/swap: unsupported package manager."
      ;;
  esac

  create_swapfile "$swap_gb" || true
  log_ok "zram/swap configured (best-effort)."
}

kernel_ge_4_9() {
  ver="$(uname -r 2>/dev/null || echo 0)"
  major="$(printf '%s' "$ver" | cut -d. -f1 | tr -cd '0-9')"
  minor="$(printf '%s' "$ver" | cut -d. -f2 | tr -cd '0-9')"
  [ -n "$major" ] || major=0
  [ -n "$minor" ] || minor=0

  if [ "$major" -gt 4 ]; then
    return 0
  fi
  if [ "$major" -eq 4 ] && [ "$minor" -ge 9 ]; then
    return 0
  fi
  return 1
}

enable_bbr() {
  log_info "Enabling BBR (if supported)..."

  if ! kernel_ge_4_9; then
    log_warn "Kernel $KERNEL may not support BBR (requires >= 4.9). Skipping."
    return 0
  fi

  if have_cmd modprobe; then
    modprobe tcp_bbr 2>/dev/null || true
  fi

  sysctl_file="/etc/sysctl.d/99-server-init.conf"
  backup_file "$sysctl_file" || true

  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] write $sysctl_file to enable BBR"
    return 0
  fi

  mkdir -p /etc/sysctl.d 2>/dev/null || true
  {
    echo "net.core.default_qdisc=fq"
    echo "net.ipv4.tcp_congestion_control=bbr"
  } >"$sysctl_file"

  if have_cmd sysctl; then
    sysctl --system >/dev/null 2>&1 || sysctl -p "$sysctl_file" >/dev/null 2>&1 || true
    sysctl net.ipv4.tcp_congestion_control 2>/dev/null || true
  fi

  log_ok "BBR configured (best-effort)."
}

install_nezha() {
  log_info "Installing Nezha agent (fixed config)..."

  if [ "$DRY_RUN" -eq 1 ]; then
    log_info "[dry-run] download Nezha installer and run with NZ_SERVER=$NZ_SERVER NZ_TLS=$NZ_TLS"
    return 0
  fi

  tmp="$(make_temp nezha-agent)"
  http_get "https://raw.githubusercontent.com/nezhahq/scripts/main/agent/install.sh" >"$tmp" || return 1
  chmod +x "$tmp" || true

  env NZ_SERVER="$NZ_SERVER" NZ_TLS="$NZ_TLS" NZ_CLIENT_SECRET="$NZ_CLIENT_SECRET" sh "$tmp"
}

human_readable_kb() {
  kb="$1"
  if [ "$kb" -ge 1048576 ]; then
    echo "$((kb / 1048576))G"
  elif [ "$kb" -ge 1024 ]; then
    echo "$((kb / 1024))M"
  else
    echo "${kb}K"
  fi
}

show_system_info() {
  log_info "System information:"

  host="$(hostname 2>/dev/null || echo unknown)"
  uptime_s="$(uptime 2>/dev/null || true)"
  cpu_model="$(awk -F: '/model name/ {gsub(/^[ \t]+/, "", $2); print $2; exit}' /proc/cpuinfo 2>/dev/null || true)"
  cpu_cores="$(getconf _NPROCESSORS_ONLN 2>/dev/null || grep -c '^processor' /proc/cpuinfo 2>/dev/null || echo 0)"
  mem_kb="$(awk '/MemTotal/ {print $2}' /proc/meminfo 2>/dev/null || echo 0)"
  mem_h="$(human_readable_kb "$mem_kb")"

  printf "  Hostname: %s\n" "$host"
  printf "  OS: %s\n" "${OS_PRETTY_NAME:-unknown}"
  printf "  Kernel: %s\n" "${KERNEL:-unknown}"
  printf "  Arch: %s\n" "${ARCH:-unknown}"
  printf "  CPU: %s (%s cores)\n" "${cpu_model:-unknown}" "${cpu_cores:-unknown}"
  printf "  Memory: %s\n" "${mem_h:-unknown}"
  if have_cmd df; then
    disk_info="$(df -h / 2>/dev/null | awk 'NR==2{printf "%s/%s used (%s)", $3, $2, $5}' || true)"
    [ -n "$disk_info" ] && printf "  Disk: %s\n" "$disk_info"
  fi
  if have_cmd ip; then
    lan_ip="$(ip -4 addr show 2>/dev/null | awk '/inet / && !/127\./ {print $2}' | head -n 1 | cut -d/ -f1)"
    [ -n "$lan_ip" ] && printf "  LAN IP: %s\n" "$lan_ip"
  fi
  [ -n "$PUBLIC_IP" ] && printf "  Public IP: %s\n" "$PUBLIC_IP"
  [ -n "$COUNTRY_CODE" ] && printf "  Country: %s\n" "$COUNTRY_CODE"
  [ -n "$uptime_s" ] && printf "  Uptime/Load: %s\n" "$uptime_s"
}

usage() {
  cat <<EOF
Usage: ${SCRIPT_NAME} [OPTIONS]

Options:
  -y, --yes           Non-interactive mode (auto confirm)
  -s, --skip MODULE   Skip a module (can be used multiple times)
  -o, --only MODULE   Only run specified module (can be used multiple times)
  --dry-run           Print what would be done, without making changes
  -h, --help          Show this help
  -v, --version       Show version

Modules (execution order):
  pubkey, location, mirrors, packages, zsh, docker, ssh, fail2ban, zram, bbr, nezha, sysinfo

Examples:
  ${SCRIPT_NAME}
  ${SCRIPT_NAME} -y
  ${SCRIPT_NAME} -y --skip zsh --skip nezha
  ${SCRIPT_NAME} --only docker --only sysinfo
EOF
}

is_valid_module() {
  case "$1" in
    pubkey|location|mirrors|packages|zsh|docker|ssh|fail2ban|zram|bbr|nezha|sysinfo) return 0 ;;
    *) return 1 ;;
  esac
}

parse_args() {
  while [ $# -gt 0 ]; do
    case "$1" in
      -y|--yes)
        AUTO_YES=1
        shift
        ;;
      --dry-run)
        DRY_RUN=1
        shift
        ;;
      -s|--skip)
        shift
        [ $# -gt 0 ] || die "--skip requires a module name"
        is_valid_module "$1" || die "Unknown module for --skip: $1"
        SKIP_MODULES="$SKIP_MODULES $1"
        shift
        ;;
      -o|--only)
        shift
        [ $# -gt 0 ] || die "--only requires a module name"
        is_valid_module "$1" || die "Unknown module for --only: $1"
        ONLY_MODULES="$ONLY_MODULES $1"
        shift
        ;;
      -h|--help)
        SUPPRESS_SUMMARY=1
        usage
        exit 0
        ;;
      -v|--version)
        SUPPRESS_SUMMARY=1
        echo "$SCRIPT_VERSION"
        exit 0
        ;;
      *)
        die "Unknown argument: $1 (use -h for help)"
        ;;
    esac
  done
}

should_run_module() {
  name="$1"
  if list_contains "$SKIP_MODULES" "$name"; then
    return 1
  fi
  if [ -n "$(trim "$ONLY_MODULES")" ]; then
    list_contains "$ONLY_MODULES" "$name" || return 1
  fi
  return 0
}

run_module() {
  name="$1"
  fn="$2"

  if ! should_run_module "$name"; then
    log_warn "Skip module: $name"
    return 0
  fi

  log_info "=== Running module: $name ==="
  "$fn"
  rc=$?
  if [ $rc -eq 0 ]; then
    log_ok "Module done: $name"
    return 0
  fi

  FAILED_MODULES="$FAILED_MODULES $name"
  log_err "Module failed: $name (exit=$rc)"

  if [ "$AUTO_YES" -eq 1 ]; then
    return 0
  fi

  if confirm "Module '$name' failed. Continue with next modules?"; then
    return 0
  fi
  exit "$rc"
}

on_exit() {
  code="$?"
  cleanup_temp
  if [ "${SUPPRESS_SUMMARY:-0}" -eq 1 ]; then
    exit "$code"
  fi
  end_ts="$(date +%s 2>/dev/null || echo 0)"
  if [ "$START_TS" -gt 0 ] && [ "$end_ts" -gt 0 ]; then
    elapsed=$((end_ts - START_TS))
  else
    elapsed="unknown"
  fi

  failed_trimmed="$(trim "$FAILED_MODULES")"
  if [ -n "$failed_trimmed" ]; then
    log_warn "Completed with failures: $failed_trimmed"
    if [ "$code" -eq 0 ]; then
      code=1
    fi
  else
    log_ok "All selected modules completed."
  fi
  log_info "Elapsed: ${elapsed}s"
  exit "$code"
}

on_interrupt() {
  log_warn "Interrupted, cleaning up..."
  cleanup_temp
  exit 130
}

main() {
  trap 'on_exit' EXIT
  trap 'on_interrupt' INT TERM

  parse_args "$@"
  require_root "$@"

  detect_system
  validate_supported_os

  run_module "pubkey" add_ssh_pubkey
  run_module "location" detect_location
  run_module "mirrors" configure_mirrors
  run_module "packages" install_packages
  run_module "zsh" configure_zsh
  run_module "docker" install_docker
  run_module "ssh" harden_ssh
  run_module "fail2ban" install_fail2ban
  run_module "zram" configure_zram_swap
  run_module "bbr" enable_bbr
  run_module "nezha" install_nezha
  run_module "sysinfo" show_system_info
}

main "$@"
