#!/usr/bin/env bash
# Wipter Manager — online installer + systemd + VPS tune + auto-update Linux
#
# Feed Linux (SoT): https://2movn.com/tools/wipter/wipter-setup.sh
# latest.json / latest.yml = Electron Windows — KHÔNG dùng cho Linux.
#
# @linux-version 3.3.17
# @linux-run wipter-v3.3.17-linux.run
#
# Cài / cập nhật bản mới nhất (mặc định, giữ data):
#   curl -fsSL https://2movn.com/tools/wipter/wipter-setup.sh | sudo bash -s -- --run
#
# Auto-update ngầm (app / timer gọi):
#   curl -fsSL https://2movn.com/tools/wipter/wipter-setup.sh | sudo bash -s -- --auto
#
# Chỉ khi muốn XÓA database và cài sạch:
#   curl -fsSL https://2movn.com/tools/wipter/wipter-setup.sh | sudo bash -s -- --wipe-data --run
#
set -euo pipefail

# systemd-run / oneshot thường không có HOME → set -u chết ở load_nvm ($HOME/.nvm)
if [ -z "${HOME:-}" ]; then
  if [ "$(id -u)" -eq 0 ]; then
    HOME=/root
  else
    HOME="$(getent passwd "$(id -un)" 2>/dev/null | cut -d: -f6)"
    HOME="${HOME:-/tmp}"
  fi
  export HOME
fi

# Pack stamp — npm run pack:linux thay số này theo package.json
WIPTER_LINUX_VERSION="${WIPTER_LINUX_VERSION:-3.3.17}"
WIPTER_LINUX_RUN="${WIPTER_LINUX_RUN:-wipter-v${WIPTER_LINUX_VERSION}-linux.run}"
BASE_URL="${WIPTER_BASE_URL:-https://2movn.com/tools/wipter}"
# Mặc định luôn latest (bản được stamp trong script này)
VERSION="${WIPTER_VERSION:-latest}"
INSTALL_DIR=""
DO_RUN=0
DO_SYSTEMD=1
DO_TUNE=1
DO_AUTO=0
DO_CHECK=0
DO_TIMER=1
# Mặc định giữ data khi update — dùng --wipe-data nếu muốn cài sạch
KEEP_DATA=1
SERVICE_NAME="wipter"
SERVICE_USER=""
PORT="${PORT:-3100}"
BIND_HOST="${BIND_HOST:-0.0.0.0}"

NODE_MAJOR_MIN=18
NODE_INSTALL_VERSION="${WIPTER_NODE_VERSION:-20}"
NVM_VERSION="${WIPTER_NVM_VERSION:-v0.40.1}"
SKIP_NODE_INSTALL=0

# Runtime dirs — không ghi đè khi keep-data (mặc định)
RUNTIME_DIRS="data toml logs proxy"
# File runtime legacy (giữ khi keep-data để boot mới migrate → SQLite rồi archive)
RUNTIME_FILES="store.json store.json.lock store.json.bak-sqlite"

RED=$'\033[0;31m'
GRN=$'\033[0;32m'
YLW=$'\033[0;33m'
CYN=$'\033[0;36m'
NC=$'\033[0m'

log()  { printf '%s[wipter-setup]%s %s\n' "$CYN" "$NC" "$*"; }
ok()   { printf '%s[wipter-setup]%s %s\n' "$GRN" "$NC" "$*"; }
warn() { printf '%s[wipter-setup]%s %s\n' "$YLW" "$NC" "$*"; }
die()  { printf '%s[wipter-setup]%s %s\n' "$RED" "$NC" "$*" >&2; exit 1; }

is_root() { [ "$(id -u)" -eq 0 ]; }

# Args gốc — cần khi re-exec ra ngoài cgroup wipter.service (bản cũ spawn setup trong service)
SETUP_ARGV=("$@")

in_named_unit_cgroup() {
  local name="$1"
  local cg
  cg="$(cat /proc/self/cgroup 2>/dev/null || true)"
  printf '%s' "$cg" | grep -qE "(^|/)${name}\\.service(\$|/)"
}

in_wipter_service_cgroup() {
  local unit
  if in_named_unit_cgroup wipter; then
    return 0
  fi
  if command -v systemctl >/dev/null 2>&1; then
    unit="$(systemctl status $$ 2>/dev/null | head -n1 || true)"
    printf '%s' "$unit" | grep -qE '(^|[[:space:]/])wipter\.service([^a-zA-Z0-9_-]|$)' && return 0
  fi
  return 1
}

# Settings Node cũ: systemd-run Type=oneshot mặc định TimeoutStartSec=90s → giết lúc đang tải .run
systemd_run_long() {
  local unit="$1" script="$2" desc="$3"
  systemctl reset-failed "${unit}.service" >/dev/null 2>&1 || true
  # Đừng stop unit đang chạy chính mình (Node wrap = wipter-apply-update)
  if ! in_named_unit_cgroup "$unit"; then
    systemctl stop "${unit}.service" >/dev/null 2>&1 || true
  fi
  systemd-run --no-block --unit="$unit" --collect \
    --description="$desc" \
    --property=Type=oneshot \
    --property=TimeoutStartSec=infinity \
    --property=TimeoutStopSec=30 \
    --property=KillMode=process \
    --property=Environment=HOME=${HOME:-/root} \
    --setenv=HOME="${HOME:-/root}" \
    /bin/bash "$script"
}

quoted_setup_args() {
  local a out=""
  for a in "${SETUP_ARGV[@]}"; do
    out+=" $(printf '%q' "$a")"
  done
  printf '%s' "$out"
}

persist_running_setup() {
  local dest="$1"
  if [ -n "${BASH_SOURCE[0]:-}" ] && [ -f "${BASH_SOURCE[0]}" ] && [ -r "${BASH_SOURCE[0]}" ]; then
    cp -f "${BASH_SOURCE[0]}" "$dest"
    return 0
  fi
  case "$0" in
    bash|-bash|sh|-sh) return 1 ;;
  esac
  if [ -f "$0" ] && [ -r "$0" ]; then
    cp -f "$0" "$dest"
    return 0
  fi
  return 1
}

# Chuyển PID hiện tại sang cgroup khác — stop wipter (KillMode=mixed) không giết updater
move_self_out_of_wipter_cgroup() {
  local dest
  if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
    dest="/sys/fs/cgroup/system.slice/wipter-apply-update.scope"
    mkdir -p "$dest" 2>/dev/null || return 1
    echo $$ >"$dest/cgroup.procs" 2>/dev/null || return 1
    in_wipter_service_cgroup && return 1
    return 0
  fi
  if [ -d /sys/fs/cgroup/systemd ]; then
    dest="/sys/fs/cgroup/systemd/system.slice/wipter-apply-update.scope"
    mkdir -p "$dest" 2>/dev/null || return 1
    echo $$ >"$dest/cgroup.procs" 2>/dev/null || return 1
    in_wipter_service_cgroup && return 1
    return 0
  fi
  return 1
}

# Nếu updater bị giết giữa chừng: 3 phút sau vẫn start lại service
schedule_start_watchdog() {
  local dest="${1:-$INSTALL_DIR}"
  local delay="${WIPTER_UPDATE_WATCHDOG_SEC:-180}"
  local wd="${dest}/logs/wipter-start-watchdog.sh"
  local start_sh
  start_sh="$(printf '%q' "${dest}/start.sh")"
  mkdir -p "${dest}/logs" 2>/dev/null || true
  cat >"$wd" <<EOF
#!/usr/bin/env bash
sleep ${delay}
if command -v systemctl >/dev/null 2>&1; then
  if ! systemctl is-active --quiet wipter.service 2>/dev/null; then
    echo "[wipter-watchdog] \$(date -u +%Y-%m-%dT%H:%M:%SZ) service down — start"
    systemctl daemon-reload || true
    systemctl start wipter.service || systemctl start wipter || true
  fi
  systemctl is-active --quiet wipter.service 2>/dev/null && exit 0
fi
if [ -x ${start_sh} ]; then
  echo "[wipter-watchdog] fallback start.sh"
  nohup ${start_sh} >/dev/null 2>&1 &
fi
EOF
  chmod +x "$wd" || true

  if is_root && command -v systemd-run >/dev/null 2>&1; then
    systemctl reset-failed wipter-update-watchdog.service >/dev/null 2>&1 || true
    systemctl stop wipter-update-watchdog.service >/dev/null 2>&1 || true
    systemd-run --no-block --unit=wipter-update-watchdog --collect \
      --description='Wipter start watchdog after update' \
      /bin/bash "$wd" >/dev/null 2>&1 || true
    return 0
  fi
  if command -v at >/dev/null 2>&1; then
    echo "/bin/bash $(printf '%q' "$wd")" | at now >/dev/null 2>&1 || true
    return 0
  fi
  nohup /bin/bash "$wd" >/dev/null 2>&1 &
}

# Chạy lại setup từ CDN ngoài cgroup — không phụ thuộc $0 (bash -s / stdin)
launch_cdn_apply_outside() {
  local job="${INSTALL_DIR}/logs/wipter-cdn-apply.sh"
  mkdir -p "${INSTALL_DIR}/logs" 2>/dev/null || true
  cat >"$job" <<EOF
#!/usr/bin/env bash
set -eu
export WIPTER_UPDATE_ESCAPED=1
export WIPTER_UPDATE_HANDOFF=1
export WIPTER_HOME=$(printf '%q' "$INSTALL_DIR")
export WIPTER_BASE_URL=$(printf '%q' "$BASE_URL")
export WIPTER_UPDATE_FEED=$(printf '%q' "${BASE_URL}/wipter-setup.sh")
TMP="\$(mktemp -t wipter-setup-cdn-XXXXXX.sh)"
FEED=${BASE_URL}/wipter-setup.sh
if command -v curl >/dev/null 2>&1; then
  curl -fsSL --max-time 60 "\$FEED" -o "\$TMP"
else
  wget -qO "\$TMP" "\$FEED"
fi
tr -d '\r' <"\$TMP" >"\$TMP.lf" && mv -f "\$TMP.lf" "\$TMP"
chmod +x "\$TMP"
exec /bin/bash "\$TMP" --auto --run --no-tune --no-timer -d $(printf '%q' "$INSTALL_DIR")
EOF
  chmod +x "$job"
  start_outside_job "$job"
}

start_outside_job() {
  local script="$1"
  if command -v systemd-run >/dev/null 2>&1; then
    # --no-block + TimeoutStartSec=infinity: Node 3.3.7–3.3.12 mặc định 90s giết lúc tải
    if systemd_run_long wipter-apply-update "$script" \
        'Wipter apply update (escaped from wipter.service)'; then
      ok "Job systemd-run --no-block wipter-apply-update (không timeout 90s)"
      return 0
    fi
    if [ "$(id -u)" -ne 0 ] && systemd-run --user --no-block --unit=wipter-apply-update --collect \
        --property=TimeoutStartSec=infinity --property=KillMode=process \
        /bin/bash "$script" 2>/dev/null; then
      ok "Job systemd-run --user wipter-apply-update"
      return 0
    fi
  fi
  if is_root && command -v systemctl >/dev/null 2>&1; then
    cat >"/etc/systemd/system/wipter-apply-update.service" <<UNIT
[Unit]
Description=Wipter Manager apply update (outside wipter.service)
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
TimeoutStartSec=infinity
KillMode=process
Nice=10
Environment=WIPTER_UPDATE_ESCAPED=1
Environment=HOME=${HOME:-/root}
ExecStart=/bin/bash ${script}
UNIT
    systemctl daemon-reload
    if systemctl start --no-block wipter-apply-update.service; then
      ok "Start wipter-apply-update.service --no-block"
      return 0
    fi
  fi
  if command -v at >/dev/null 2>&1; then
    if echo "/bin/bash $(printf '%q' "$script")" | at now; then
      ok "Job at now"
      return 0
    fi
  fi
  return 1
}

# 3.3.7 / 3.3.8 spawn setup trong wipter.service → stop (KillMode=mixed) giết updater.
# Tách TRƯỚC khi stop: chuyển cgroup tại chỗ, hoặc job ngoài (--no-block).
leave_wipter_cgroup_or_reexec() {
  [ "${WIPTER_UPDATE_ESCAPED:-0}" = "1" ] && return 0
  in_wipter_service_cgroup || return 0

  log "Đang trong wipter.service — tách cgroup (compat auto-update 3.3.7/3.3.8)"
  mkdir -p "${INSTALL_DIR}/logs" 2>/dev/null || true

  # 1) Giữ nguyên process (bash -s / stdin vẫn chạy tiếp)
  if move_self_out_of_wipter_cgroup; then
    export WIPTER_UPDATE_ESCAPED=1
    ok "Đã chuyển PID $$ ra khỏi wipter.service (cgroup.procs)"
    return 0
  fi

  local persist continue_sh
  persist="${INSTALL_DIR}/logs/wipter-setup-escaped.sh"
  continue_sh="${INSTALL_DIR}/logs/wipter-setup-continue.sh"

  # 2) Copy script đang chạy + job ngoài cgroup
  if persist_running_setup "$persist"; then
    chmod +x "$persist" || true
    cat >"$continue_sh" <<EOF
#!/usr/bin/env bash
set -eu
export WIPTER_UPDATE_ESCAPED=1
export WIPTER_HOME=$(printf '%q' "$INSTALL_DIR")
export WIPTER_BASE_URL=$(printf '%q' "$BASE_URL")
export WIPTER_UPDATE_FEED=$(printf '%q' "${BASE_URL}/wipter-setup.sh")
export WIPTER_VERSION=$(printf '%q' "${VERSION:-latest}")
exec /bin/bash $(printf '%q' "$persist")$(quoted_setup_args)
EOF
    chmod +x "$continue_sh"
    if start_outside_job "$continue_sh"; then
      ok "Đã giao job ngoài service — thoát process trong wipter.service"
      exit 0
    fi
  else
    warn "Không lưu được script (curl|bash stdin) — thử tải lại từ CDN ngoài cgroup"
  fi

  # 3) Không phụ thuộc $0: curl feed lần nữa trong unit riêng
  if launch_cdn_apply_outside; then
    ok "Đã giao job CDN ngoài service — thoát process trong wipter.service"
    exit 0
  fi

  warn "Không tách được cgroup — tiếp tục tại chỗ (watchdog sẽ start lại nếu bị giết)"
}

# Settings gọi systemd-run wipter-apply-update (oneshot 90s). Tách sang unit dài rồi thoát
# để process cha kết thúc trước timeout — tải .run không bị SIGKILL.
handoff_auto_long_job() {
  [ "${DO_AUTO:-0}" -eq 1 ] || return 0
  [ "${WIPTER_UPDATE_HANDOFF:-0}" = "1" ] && return 0

  local need=0
  in_wipter_service_cgroup && need=1
  in_named_unit_cgroup wipter-apply-update && need=1
  # UI / timer / curl|bash: stdin không phải tty
  if [ "$need" -eq 0 ] && [ ! -t 0 ]; then
    need=1
  fi
  [ "$need" -eq 1 ] || return 0

  log "Tách job cập nhật khỏi unit 90s (Settings) — tải .run không bị cắt"
  mkdir -p "${INSTALL_DIR}/logs" 2>/dev/null || true
  local persist continue_sh
  persist="${INSTALL_DIR}/logs/wipter-setup-handoff.sh"
  continue_sh="${INSTALL_DIR}/logs/wipter-setup-handoff-run.sh"

  if persist_running_setup "$persist"; then
    chmod +x "$persist" || true
    cat >"$continue_sh" <<EOF
#!/usr/bin/env bash
set -eu
export WIPTER_UPDATE_HANDOFF=1
export WIPTER_UPDATE_ESCAPED=1
export WIPTER_HOME=$(printf '%q' "$INSTALL_DIR")
export WIPTER_BASE_URL=$(printf '%q' "$BASE_URL")
export WIPTER_UPDATE_FEED=$(printf '%q' "${BASE_URL}/wipter-setup.sh")
export WIPTER_VERSION=$(printf '%q' "${VERSION:-latest}")
exec /bin/bash $(printf '%q' "$persist")$(quoted_setup_args)
EOF
  else
    cat >"$continue_sh" <<EOF
#!/usr/bin/env bash
set -eu
export WIPTER_UPDATE_HANDOFF=1
export WIPTER_UPDATE_ESCAPED=1
export WIPTER_HOME=$(printf '%q' "$INSTALL_DIR")
export WIPTER_BASE_URL=$(printf '%q' "$BASE_URL")
export WIPTER_UPDATE_FEED=$(printf '%q' "${BASE_URL}/wipter-setup.sh")
TMP="\$(mktemp -t wipter-setup-handoff-XXXXXX.sh)"
FEED=${BASE_URL}/wipter-setup.sh
if command -v curl >/dev/null 2>&1; then
  curl -fsSL --max-time 60 "\$FEED" -o "\$TMP"
else
  wget -qO "\$TMP" "\$FEED"
fi
tr -d '\\r' <"\$TMP" >"\$TMP.lf" && mv -f "\$TMP.lf" "\$TMP"
chmod +x "\$TMP"
exec /bin/bash "\$TMP" --auto --run --no-tune --no-timer -d $(printf '%q' "$INSTALL_DIR")
EOF
  fi
  chmod +x "$continue_sh"

  if command -v systemd-run >/dev/null 2>&1 \
     && systemd_run_long wipter-apply-run "$continue_sh" 'Wipter apply update (long, no 90s cap)'; then
    ok "Đã giao wipter-apply-run — thoát process Settings (tránh timeout)"
    exit 0
  fi

  if is_root && command -v systemctl >/dev/null 2>&1; then
    cat >"/etc/systemd/system/wipter-apply-run.service" <<UNIT
[Unit]
Description=Wipter apply update (long job)
After=network-online.target

[Service]
Type=oneshot
TimeoutStartSec=infinity
TimeoutStopSec=30
KillMode=process
Environment=WIPTER_UPDATE_HANDOFF=1
Environment=WIPTER_UPDATE_ESCAPED=1
Environment=HOME=${HOME:-/root}
ExecStart=/bin/bash ${continue_sh}
UNIT
    systemctl daemon-reload
    if systemctl start --no-block wipter-apply-run.service; then
      ok "Đã start wipter-apply-run.service — thoát process Settings"
      exit 0
    fi
  fi

  if command -v at >/dev/null 2>&1; then
    if echo "WIPTER_UPDATE_HANDOFF=1 /bin/bash $(printf '%q' "$continue_sh")" | at now; then
      ok "Job at now (handoff) — thoát process Settings"
      exit 0
    fi
  fi

  warn "Không tách được job dài — tiếp tục tại chỗ"
}

ensure_wipter_started() {
  local dest="${1:-$INSTALL_DIR}"
  local i
  if command -v systemctl >/dev/null 2>&1 && \
     { [ -f "/etc/systemd/system/${SERVICE_NAME}.service" ] || \
       systemctl cat "${SERVICE_NAME}.service" >/dev/null 2>&1; }; then
    systemctl daemon-reload || true
    systemctl enable "${SERVICE_NAME}.service" >/dev/null 2>&1 || true
    for i in 1 2 3 4 5; do
      systemctl restart "${SERVICE_NAME}.service" 2>/dev/null \
        || systemctl start "${SERVICE_NAME}.service" 2>/dev/null \
        || systemctl start "${SERVICE_NAME}" 2>/dev/null || true
      sleep 2
      if systemctl is-active --quiet "${SERVICE_NAME}.service" 2>/dev/null \
         || systemctl is-active --quiet "${SERVICE_NAME}" 2>/dev/null; then
        ok "Service ${SERVICE_NAME} đang chạy"
        return 0
      fi
    done
    warn "systemd chưa active — fallback ${dest}/start.sh"
  fi
  if [ -x "${dest}/start.sh" ]; then
    nohup "${dest}/start.sh" >/dev/null 2>&1 &
    ok "Đã start.sh nền (PID $!)"
    return 0
  fi
  return 1
}

usage() {
  cat <<EOF
Wipter Manager — cài đặt / cập nhật Linux (keep-data mặc định)

Feed: ${BASE_URL}/wipter-setup.sh
latest.json / latest.yml = Electron Windows — không dùng trên VPS.

Mặc định: bản mới nhất v${WIPTER_LINUX_VERSION} (${WIPTER_LINUX_RUN}).

Usage:
  curl -fsSL ${BASE_URL}/wipter-setup.sh | sudo bash -s -- [options]

Options:
  -v, --version <ver>     latest (mặc định) hoặc 3.3.17 / v3.3.17-linux
  -d, --dir <path>        Thư mục cài (mặc định: /opt/wipter nếu root, else ~/wipter)
  --user <name>           User chạy service
  --port <n>              PORT (mặc định 3100)
  --run                   Enable + start service ngay
  --auto                  Cập nhật ngầm: keep-data + --run + không tune lại
  --check                 In version stamp rồi thoát (app dùng để so sánh)
  --keep-data, --update   Giữ database + runtime (ĐÃ LÀ MẶC ĐỊNH)
  --wipe-data, --fresh    Cài sạch — data → *.bak-<ts>
  --no-systemd            Không cài systemd unit
  --no-tune               Không áp dụng tối ưu VPS
  --no-timer              Không cài wipter-update.timer
  --no-node-install       Không tự cài nvm/Node
  -h, --help              Trợ giúp

Examples:
  curl -fsSL ${BASE_URL}/wipter-setup.sh | sudo bash -s -- --run
  curl -fsSL ${BASE_URL}/wipter-setup.sh | sudo bash -s -- --auto
  curl -fsSL ${BASE_URL}/wipter-setup.sh | sudo bash -s -- --wipe-data --run
EOF
}

normalize_linux_ver() {
  local v="${1:-}"
  v="${v#wipter-}"
  v="${v#v}"
  v="${v%-linux.run}"
  v="${v%-linux}"
  printf '%s' "$v"
}

resolve_run_name() {
  local raw="${1:-latest}"
  case "$raw" in
    latest|""|current)
      echo "$WIPTER_LINUX_RUN"
      ;;
    v3.3-linux|v3.3|3.3)
      echo "$WIPTER_LINUX_RUN"
      ;;
    *)
      echo "wipter-v$(normalize_linux_ver "$raw")-linux.run"
      ;;
  esac
}

need_cmd() {
  command -v "$1" >/dev/null 2>&1 || die "Thiếu lệnh bắt buộc: $1"
}

download() {
  local url="$1" dest="$2"
  if command -v curl >/dev/null 2>&1; then
    curl -fL --progress-bar -o "$dest" "$url" || die "Tải thất bại: $url"
  elif command -v wget >/dev/null 2>&1; then
    wget -O "$dest" "$url" || die "Tải thất bại: $url"
  else
    die "Cần curl hoặc wget"
  fi
}

node_major() {
  command -v node >/dev/null 2>&1 || { echo 0; return; }
  node -p "process.versions.node.split('.')[0]" 2>/dev/null || echo 0
}

load_nvm() {
  local dir="${NVM_DIR:-${HOME:-/root}/.nvm}"
  export NVM_DIR="$dir"
  # shellcheck disable=SC1090,SC1091
  [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
  # shellcheck disable=SC1090,SC1091
  [ -s "$NVM_DIR/bash_completion" ] && . "$NVM_DIR/bash_completion" || true
}

symlink_node_system() {
  local node_path npm_path npx_path
  node_path="$(command -v node || true)"
  npm_path="$(command -v npm || true)"
  npx_path="$(command -v npx || true)"
  [ -n "$node_path" ] || return 1

  # Symlink vào /usr/local/bin để systemd / non-interactive shell thấy được
  if is_root; then
    mkdir -p /usr/local/bin
    ln -sfn "$node_path" /usr/local/bin/node
    [ -n "$npm_path" ] && ln -sfn "$npm_path" /usr/local/bin/npm || true
    [ -n "$npx_path" ] && ln -sfn "$npx_path" /usr/local/bin/npx || true
    hash -r 2>/dev/null || true
    ok "Đã symlink node → /usr/local/bin/node"
  fi
  return 0
}

install_nvm_and_node() {
  local major want="$NODE_INSTALL_VERSION"
  major="$(node_major)"
  if [ "${major:-0}" -ge "$NODE_MAJOR_MIN" ]; then
    ok "Node $(node -v) OK (>= $NODE_MAJOR_MIN)"
    symlink_node_system || true
    return 0
  fi

  if [ "$SKIP_NODE_INSTALL" -eq 1 ]; then
    die "Chưa có Node.js >= $NODE_MAJOR_MIN và đã tắt --no-node-install"
  fi

  need_cmd curl
  # nvm cần vài tool cơ bản
  if is_root; then
    if command -v apt-get >/dev/null 2>&1; then
      log "Cài dependency (curl, ca-certificates, build-essential nếu thiếu)…"
      export DEBIAN_FRONTEND=noninteractive
      apt-get update -y >/dev/null 2>&1 || true
      apt-get install -y curl ca-certificates >/dev/null 2>&1 || true
    elif command -v yum >/dev/null 2>&1; then
      yum install -y curl ca-certificates >/dev/null 2>&1 || true
    fi
  fi

  export NVM_DIR="${NVM_DIR:-${HOME:-/root}/.nvm}"
  if [ ! -s "$NVM_DIR/nvm.sh" ]; then
    log "Cài nvm ${NVM_VERSION} vào $NVM_DIR …"
    curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh" | bash
  else
    ok "nvm đã có tại $NVM_DIR"
  fi

  load_nvm
  command -v nvm >/dev/null 2>&1 || die "Cài nvm thất bại (không load được nvm.sh)"

  log "Cài Node.js ${want} (LTS khuyến nghị) qua nvm…"
  nvm install "$want"
  nvm alias default "$want"
  nvm use default >/dev/null

  major="$(node_major)"
  [ "${major:-0}" -ge "$NODE_MAJOR_MIN" ] || die "Sau khi cài nvm vẫn không có Node >= $NODE_MAJOR_MIN"

  # Persist cho login shell
  if is_root; then
    if [ -f /root/.bashrc ] && ! grep -q 'NVM_DIR' /root/.bashrc 2>/dev/null; then
      cat >>/root/.bashrc <<'EOS'

# nvm (Wipter setup)
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
EOS
    fi
  fi

  symlink_node_system
  ok "Node $(node -v) — npm $(npm -v 2>/dev/null || echo '?')"
}

ensure_node() {
  # Load nvm nếu có sẵn rồi mới kiểm tra
  load_nvm || true
  local major
  major="$(node_major)"
  if [ "${major:-0}" -ge "$NODE_MAJOR_MIN" ]; then
    ok "Node $(node -v) OK"
    symlink_node_system || true
    return 0
  fi
  install_nvm_and_node
}

# ---------- VPS tune ----------
apply_vps_tune() {
  if ! is_root; then
    warn "Bỏ qua tối ưu VPS (cần root). Chạy lại bằng sudo để áp dụng."
    return 0
  fi

  log "Áp dụng tối ưu VPS (sysctl + limits)…"

  cat >/etc/sysctl.d/99-wipter.conf <<'EOF'
# Wipter Manager — kernel / network / process limits
# file descriptors & pid
fs.file-max = 2097152
fs.nr_open = 2097152
kernel.pid_max = 4194304
kernel.threads-max = 2097152

# inotify — tránh "Failed to allocate directory watch: Too many open files"
# (systemd + nhiều toml/tunnel/node mở watch)
fs.inotify.max_user_watches = 2097152
fs.inotify.max_user_instances = 8192
fs.inotify.max_queued_events = 65536

# network backlog / sockets
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 250000
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 1048576
net.core.wmem_default = 1048576
net.core.optmem_max = 65536

# TCP
net.ipv4.tcp_rmem = 4096 1048576 16777216
net.ipv4.tcp_wmem = 4096 1048576 16777216
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_max_tw_buckets = 2000000
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_mtu_probing = 1

# conntrack (nếu module có)
net.netfilter.nf_conntrack_max = 1048576

# Swap sớm: ~70–80% RAM used (chi tiết tính extra_free trong wipter-prefer-swap.sh)
vm.swappiness = 200
vm.vfs_cache_pressure = 200
vm.watermark_scale_factor = 1000
vm.page-cluster = 0
EOF

  # Áp dụng (bỏ qua key không tồn tại trên kernel tối giản)
  if sysctl --system >/dev/null 2>&1; then
    ok "sysctl --system OK"
  else
    sysctl -p /etc/sysctl.d/99-wipter.conf >/dev/null 2>&1 || true
    warn "sysctl một phần (một số key có thể không hỗ trợ trên kernel này)"
  fi

  # limits.d — nofile / nproc cho root và wildcard
  cat >/etc/security/limits.d/99-wipter.conf <<'EOF'
# Wipter Manager — ulimit
*       soft    nofile  1048576
*       hard    nofile  1048576
*       soft    nproc   65535
*       hard    nproc   65535
root    soft    nofile  1048576
root    hard    nofile  1048576
root    soft    nproc   unlimited
root    hard    nproc   unlimited
EOF

  # systemd mặc định cũng giới hạn NOFILE — soft:hard (cần daemon-reexec)
  mkdir -p /etc/systemd/system.conf.d
  cat >/etc/systemd/system.conf.d/99-wipter.conf <<'EOF'
[Manager]
DefaultLimitNOFILE=1048576:1048576
DefaultLimitNPROC=65535:infinity
DefaultTasksMax=infinity
EOF

  # pam_limits (nếu có) — đảm bảo login shell nhận limits
  if [ -d /etc/pam.d ]; then
    for f in /etc/pam.d/common-session /etc/pam.d/common-session-noninteractive; do
      if [ -f "$f" ] && ! grep -q 'pam_limits.so' "$f" 2>/dev/null; then
        echo 'session required pam_limits.so' >>"$f"
      fi
    done
  fi

  if command -v systemctl >/dev/null 2>&1; then
    systemctl daemon-reexec >/dev/null 2>&1 || true
  fi

  ok "Đã ghi:"
  ok "  /etc/sysctl.d/99-wipter.conf"
  ok "  /etc/security/limits.d/99-wipter.conf"
  ok "  /etc/systemd/system.conf.d/99-wipter.conf"
}

is_runtime_dir() {
  local name="$1" d
  for d in $RUNTIME_DIRS; do
    [ "$name" = "$d" ] && return 0
  done
  return 1
}

is_runtime_file() {
  local name="$1" f
  for f in $RUNTIME_FILES; do
    [ "$name" = "$f" ] && return 0
  done
  return 1
}

# Dừng service/process đang giữ bin/wipter-tunnel (tránh "Text file busy")
# --auto: KHÔNG systemctl stop / stop.sh — KillMode=mixed giết luôn updater (đang tải/extract).
# Unlink binary là đủ (Linux cho xóa file đang chạy); restart ở bước cuối.
stop_running_install() {
  local dest="$1"
  local tunnel="$dest/bin/wipter-tunnel"
  local did=0

  if [ "${DO_AUTO:-0}" -eq 1 ]; then
    log "Auto-update: giữ service chạy — chỉ gỡ tunnel (không stop wipter)"
    rm -f "$tunnel" 2>/dev/null || true
    if [ -e "$tunnel" ] && command -v fuser >/dev/null 2>&1; then
      fuser -k "$tunnel" >/dev/null 2>&1 || true
    fi
    return 0
  fi

  if command -v systemctl >/dev/null 2>&1; then
    if [ -f "/etc/systemd/system/${SERVICE_NAME}.service" ] || \
       systemctl cat "${SERVICE_NAME}.service" >/dev/null 2>&1; then
      if systemctl is-active --quiet "${SERVICE_NAME}.service" 2>/dev/null; then
        log "Dừng systemd ${SERVICE_NAME} trước khi cập nhật…"
        systemctl stop "${SERVICE_NAME}.service" || true
        did=1
      fi
    fi
  fi

  if [ -x "$dest/stop.sh" ]; then
    bash "$dest/stop.sh" >/dev/null 2>&1 || true
    did=1
  else
    pkill -f "$dest/boot-sealed.js" 2>/dev/null || true
    pkill -f "$dest/app/src/server.js" 2>/dev/null || true
    pkill -f "wipter-tunnel.*$dest" 2>/dev/null || true
    pkill -f "$dest/bin/wipter-tunnel" 2>/dev/null || true
  fi

  # Giải phóng file binary nếu còn process giữ
  if [ -e "$tunnel" ] && command -v fuser >/dev/null 2>&1; then
    local i
    for i in 1 2 3 4 5 6 7 8 9 10; do
      fuser "$tunnel" >/dev/null 2>&1 || break
      fuser -k "$tunnel" >/dev/null 2>&1 || true
      sleep 0.4
    done
  else
    sleep 1
  fi

  if [ "$did" -eq 1 ]; then
    ok "Đã dừng process cũ (tránh Text file busy)"
  fi
}

# ---------- extract ----------
extract_run_to() {
  local run_file="$1" dest="$2"
  local tmp data_backup="" src item base
  tmp="$(mktemp -d -t wipter-extract-XXXXXX)"

  need_cmd base64
  need_cmd tar
  need_cmd awk

  # Dòng thực tế trong .run: DATA_BASE64="$(cat <<'EOF'
  # → khớp theo substring, rồi stream base64 (không nhét 80MB vào biến bash)
  if ! awk '
      BEGIN { show=0 }
      index($0, "<<'\''EOF'\''") { show=1; next }
      show && $0 == "EOF" { exit }
      show { print }
    ' "$run_file" | base64 -d | tar -xz -C "$tmp"
  then
    rm -rf "$tmp"
    die "Giải nén .run thất bại (base64/tar). Kiểm tra file tải về có bị cắt không."
  fi

  [ -d "$tmp/linux-standalone" ] || {
    rm -rf "$tmp"
    die "Archive trong .run không chứa thư mục linux-standalone"
  }

  mkdir -p "$dest"
  src="$tmp/linux-standalone"

  # Unlink binary cũ trước khi ghi — Linux cho phép xóa file đang chạy; overwrite thì ETXTBSY
  if [ -e "$dest/bin/wipter-tunnel" ] || [ -d "$dest/bin" ]; then
    rm -f "$dest/bin/wipter-tunnel" 2>/dev/null || true
    rm -rf "$dest/bin" 2>/dev/null || true
  fi

  if [ "$KEEP_DATA" -eq 1 ]; then
    # Chỉ cập nhật code — không đụng data/ (sqlite, .store_key) và runtime dirs
    # Lưu ý: phải copy cả file ẩn (.seal_key) — nếu thiếu, boot-sealed decrypt fail ngay.
    log "Chế độ keep-data (mặc định): chỉ cập nhật code, giữ database + runtime"
    (
      shopt -s nullglob dotglob
      for item in "$src"/*; do
        base="$(basename "$item")"
        case "$base" in .|..) continue ;; esac
        if is_runtime_dir "$base"; then
          mkdir -p "$dest/$base"
          continue
        fi
        if is_runtime_file "$base"; then
          # Giữ store.json (fingerprint/deviceInfo) — không ghi đè từ pack
          continue
        fi
        if [ -d "$item" ]; then
          rm -rf "$dest/$base"
          cp -a "$item" "$dest/$base"
        else
          rm -f "$dest/$base" 2>/dev/null || true
          cp -a "$item" "$dest/$base"
        fi
      done
    )
    for item in $RUNTIME_DIRS; do
      mkdir -p "$dest/$item"
    done
    # Đảm bảo cặp seal khớp (payload.sealed + .seal_key cùng bản)
    if [ -f "$src/.seal_key" ]; then
      cp -a "$src/.seal_key" "$dest/.seal_key"
      chmod 600 "$dest/.seal_key" 2>/dev/null || true
    fi
    if [ -f "$src/payload.sealed" ]; then
      cp -a "$src/payload.sealed" "$dest/payload.sealed"
    fi
    if [ -f "$dest/data/wipter.sqlite" ] || [ -f "$dest/data/.store_key" ]; then
      ok "Đã giữ data cũ (wipter.sqlite / .store_key)"
    else
      warn "Chưa có database tại $dest/data — cài mới (thư mục data trống)"
    fi
    if [ -f "$dest/boot-sealed.js" ] && { [ ! -f "$dest/.seal_key" ] || [ ! -f "$dest/payload.sealed" ]; }; then
      warn "Thiếu .seal_key hoặc payload.sealed — service sealed sẽ không start được"
    fi
  else
    # F-17: --wipe-data = cài sạch. Backup data cũ sang data.bak-<ts> để rollback tay.
    # KHÔNG restore lại vào data/ (bug cũ: wipe rồi vẫn copy backup về).
    local wipe_bak=""
    if [ -d "$dest/data" ] && [ "$(ls -A "$dest/data" 2>/dev/null | wc -l)" -gt 0 ]; then
      wipe_bak="${dest}/data.bak-$(date +%Y%m%d%H%M%S)"
      mv "$dest/data" "$wipe_bak"
      warn "Wipe-data: đã chuyển data cũ → $wipe_bak (không khôi phục tự động)"
    fi
    for item in toml logs proxy; do
      if [ -d "$dest/$item" ] && [ "$(ls -A "$dest/$item" 2>/dev/null | wc -l)" -gt 0 ]; then
        local rb="${dest}/${item}.bak-$(date +%Y%m%d%H%M%S)"
        mv "$dest/$item" "$rb" 2>/dev/null || rm -rf "$dest/$item"
        warn "Wipe-data: đã chuyển $item → ${rb:-removed}"
      fi
    done

    cp -a "$src/." "$dest/"

    # Đảm bảo data/ trống sạch từ pack (hoặc tạo mới)
    mkdir -p "$dest/data" "$dest/toml" "$dest/logs" "$dest/proxy" "$dest/bin"
    if [ -n "$wipe_bak" ]; then
      ok "Cài sạch xong. Rollback tay: mv $wipe_bak $dest/data"
    else
      ok "Cài sạch (không có data cũ)"
    fi
  fi

  rm -rf "$tmp"

  chmod +x "$dest/run.sh" "$dest/stop.sh" 2>/dev/null || true
  [ -f "$dest/bin/wipter-tunnel" ] && chmod +x "$dest/bin/wipter-tunnel" || true
}

write_auto_update_helper() {
  local dest="$1"
  # Script đầy đủ nằm trong pack (scripts/wipter-auto-update.sh). Fallback ngắn nếu thiếu.
  if [ -f "$dest/wipter-auto-update.sh" ] && grep -q 'parse_remote_version' "$dest/wipter-auto-update.sh" 2>/dev/null; then
    chmod +x "$dest/wipter-auto-update.sh" || true
    return 0
  fi
  cat >"$dest/wipter-auto-update.sh" <<EOS
#!/usr/bin/env bash
set -euo pipefail
FEED_URL="\${WIPTER_UPDATE_FEED:-${BASE_URL}/wipter-setup.sh}"
INSTALL_DIR="\${WIPTER_HOME:-$dest}"
LOG="\${WIPTER_UPDATE_LOG:-\$INSTALL_DIR/logs/auto-update.log}"
mkdir -p "\$(dirname "\$LOG")" 2>/dev/null || true
    if [ "\${WIPTER_UPDATE_ESCAPED:-0}" != "1" ] && grep -qE '(^|/)wipter\\.service(\$|/)' /proc/self/cgroup 2>/dev/null; then
  export WIPTER_UPDATE_ESCAPED=1
  SELF="\$(readlink -f "\$0" 2>/dev/null || echo "\$0")"
  if command -v systemd-run >/dev/null 2>&1; then
    systemctl reset-failed wipter-apply-update.service >/dev/null 2>&1 || true
    systemd-run --no-block --unit=wipter-apply-update --collect /bin/bash "\$SELF" "\$@" && exit 0
  fi
fi
LOCK="\${TMPDIR:-/tmp}/wipter-auto-update.lock"
if command -v flock >/dev/null 2>&1; then
  exec 9>"\$LOCK"
  flock -n 9 || exit 0
fi
BODY="\$(curl -fsSL --max-time 45 "\$FEED_URL" 2>/dev/null || wget -qO- --timeout=45 "\$FEED_URL")"
printf '%s' "\$BODY" | grep -q 'WIPTER_LINUX_VERSION\\|@linux-version' || exit 1
REMOTE="\$(printf '%s\\n' "\$BODY" | sed -n 's/^# @linux-version //p' | head -n1 | tr -d '\\r')"
LOCAL="\$(tr -d ' \\r\\n' <"\$INSTALL_DIR/.linux-version" 2>/dev/null || true)"
if [ -n "\$LOCAL" ] && [ "\$REMOTE" = "\$LOCAL" ]; then
  exit 0
fi
TMP="\$(mktemp -t wipter-setup-XXXXXX.sh)"
printf '%s\\n' "\$BODY" >"\$TMP"
chmod +x "\$TMP"
export WIPTER_BASE_URL="${BASE_URL}"
export WIPTER_HOME="\$INSTALL_DIR"
set +e
bash "\$TMP" --auto --run --no-tune --no-timer -d "\$INSTALL_DIR" >>"\$LOG" 2>&1
RC=\$?
if command -v systemctl >/dev/null 2>&1; then
  systemctl start wipter.service || systemctl start wipter || true
fi
rm -f "\$TMP"
exit \$RC
EOS
  chmod +x "$dest/wipter-auto-update.sh" || true
}

install_update_timer() {
  local dest="$1"
  [ "$DO_TIMER" -eq 1 ] || return 0
  command -v systemctl >/dev/null 2>&1 || return 0
  is_root || return 0
  [ -x "$dest/wipter-auto-update.sh" ] || return 0

  cat >"/etc/systemd/system/wipter-update.service" <<EOF
[Unit]
Description=Wipter Manager silent Linux update (wipter-setup.sh)
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
WorkingDirectory=${dest}
Environment=WIPTER_HOME=${dest}
Environment=WIPTER_BASE_URL=${BASE_URL}
Environment=WIPTER_UPDATE_FEED=${BASE_URL}/wipter-setup.sh
ExecStart=${dest}/wipter-auto-update.sh
Nice=10
EOF

  cat >"/etc/systemd/system/wipter-update.timer" <<'EOF'
[Unit]
Description=Wipter Manager — check Linux update every 6h

[Timer]
OnBootSec=15min
OnUnitActiveSec=6h
Persistent=true
RandomizedDelaySec=10min

[Install]
WantedBy=timers.target
EOF

  systemctl daemon-reload
  systemctl enable --now wipter-update.timer >/dev/null 2>&1 || true
  ok "Đã bật wipter-update.timer (6h, keep-data)"
}

write_wrappers() {
  local dest="$1" ver="$2" run_name="$3"

  cat >"$dest/start.sh" <<EOF
#!/usr/bin/env bash
set -euo pipefail
DIR="\$(cd "\$(dirname "\$0")" && pwd)"
cd "\$DIR"
export PORT="\${PORT:-$PORT}"
export BIND_HOST="\${BIND_HOST:-$BIND_HOST}"
ulimit -n 1048576 2>/dev/null || true
exec ./run.sh "\$@"
EOF
  chmod +x "$dest/start.sh"

  printf '%s\n' "$WIPTER_LINUX_VERSION" >"$dest/.linux-version"
  cat >"$dest/INSTALL.txt" <<EOF
Wipter Manager — đã cài đặt
Version pack : $ver
Run file     : $run_name
Linux stamp  : $WIPTER_LINUX_VERSION
Install dir  : $dest
Service      : ${SERVICE_NAME}.service
Installed at : $(date -u +%Y-%m-%dT%H:%M:%SZ)
Update feed  : ${BASE_URL}/wipter-setup.sh

Systemd:
  systemctl status ${SERVICE_NAME}
  systemctl start ${SERVICE_NAME}
  systemctl stop ${SERVICE_NAME}
  systemctl restart ${SERVICE_NAME}
  journalctl -u ${SERVICE_NAME} -f

Thủ công:
  $dest/start.sh
  $dest/stop.sh

Web UI:
  http://0.0.0.0:${PORT}/web/

Yêu cầu: Node.js >= 18
EOF
}

install_systemd() {
  local dest="$1"
  local user="$2"

  if ! command -v systemctl >/dev/null 2>&1; then
    warn "Không có systemctl — bỏ qua cài service"
    return 1
  fi
  if ! is_root; then
    warn "Cần root để cài systemd system service. Dùng: sudo bash …"
    return 1
  fi

  local node_bin
  # Ưu tiên /usr/local/bin/node (symlink sau nvm), rồi PATH
  if [ -x /usr/local/bin/node ]; then
    node_bin="/usr/local/bin/node"
  else
    node_bin="$(command -v node || true)"
  fi
  [ -n "$node_bin" ] || die "Không tìm thấy node trong PATH (cần Node >= 18)"
  ok "systemd dùng node: $node_bin ($("$node_bin" -v 2>/dev/null || true))"

  # Entry: ưu tiên boot-sealed nếu có (bản seal), else run.sh
  local exec_start
  if [ -f "$dest/boot-sealed.js" ]; then
    exec_start="$node_bin $dest/boot-sealed.js"
  else
    exec_start="$dest/run.sh"
  fi

  mkdir -p /etc/systemd/system

  # Heap main process theo MemTotal (~25% RAM, clamp 384–2048) — tránh cứng 2048 trên VPS 1–2GB
  local ram_kb=0 heap_mb=768
  ram_kb=$(awk '/MemTotal:/ {print $2; exit}' /proc/meminfo 2>/dev/null || echo 0)
  if [ "${ram_kb:-0}" -gt 0 ] 2>/dev/null; then
    heap_mb=$(( ram_kb / 1024 * 25 / 100 ))
    [ "$heap_mb" -lt 384 ] && heap_mb=384
    [ "$heap_mb" -gt 2048 ] && heap_mb=2048
  fi
  ok "NODE_OPTIONS max-old-space-size=${heap_mb} (theo RAM)"

  cat >"/etc/systemd/system/${SERVICE_NAME}.service" <<EOF
[Unit]
Description=Wipter Manager (${VERSION})
Documentation=https://2movn.com/tools/wipter
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=60
StartLimitBurst=10

[Service]
Type=simple
User=${user}
Group=${user}
WorkingDirectory=${dest}
Environment=NODE_ENV=production
Environment=WIPTER_HOME=${dest}
Environment=SQLITE_PATH=${dest}/data/wipter.sqlite
Environment=TOML_DIR=${dest}/toml
Environment=BIN_DIR=${dest}/bin
Environment=PORT=${PORT}
Environment=BIND_HOST=${BIND_HOST}
# UV_THREADPOOL_SIZE: app smart-pick 4–32 (uv-threadpool.js). Ép tay: UV_THREADPOOL_SIZE=N + WIPTER_UV_FORCE=1
Environment=WIPTER_PROFILE=auto
Environment=NODE_OPTIONS=--max-old-space-size=${heap_mb}
# FD / process limits trong service (soft:hard)
LimitNOFILE=1048576:1048576
LimitNPROC=infinity
TasksMax=infinity
OOMPolicy=continue
# Tự restart khi crash / exit lỗi
Restart=always
RestartSec=5
TimeoutStartSec=120
TimeoutStopSec=90
KillMode=mixed
KillSignal=SIGTERM
# Giữ log gọn
StandardOutput=journal
StandardError=journal
SyslogIdentifier=${SERVICE_NAME}

ExecStartPre=/bin/bash -c 'ulimit -n 1048576 2>/dev/null || true; mkdir -p "${dest}/data" "${dest}/toml" "${dest}/logs" "${dest}/bin" "${dest}/proxy"; chmod +x "${dest}/run.sh" "${dest}/stop.sh" 2>/dev/null || true; [ -f "${dest}/bin/wipter-tunnel" ] && chmod +x "${dest}/bin/wipter-tunnel" || true'
ExecStart=${exec_start}

[Install]
WantedBy=multi-user.target
EOF

  # Quyền thư mục nếu chạy bằng user khác root
  if [ "$user" != "root" ]; then
    if id "$user" >/dev/null 2>&1; then
      chown -R "$user:$user" "$dest"
    else
      die "User không tồn tại: $user"
    fi
  fi

  systemctl daemon-reload
  systemctl enable "${SERVICE_NAME}.service"
  ok "Đã cài systemd: ${SERVICE_NAME}.service (enable on boot + Restart=always)"
  return 0
}

# ---------- parse args ----------
while [ $# -gt 0 ]; do
  case "$1" in
    -v|--version)
      [ $# -ge 2 ] || die "Thiếu giá trị cho $1"
      VERSION="$2"; shift 2 ;;
    -d|--dir|--home)
      [ $# -ge 2 ] || die "Thiếu giá trị cho $1"
      INSTALL_DIR="$2"; shift 2 ;;
    --user)
      [ $# -ge 2 ] || die "Thiếu giá trị cho $1"
      SERVICE_USER="$2"; shift 2 ;;
    --port)
      [ $# -ge 2 ] || die "Thiếu giá trị cho $1"
      PORT="$2"; shift 2 ;;
    --run)
      DO_RUN=1; shift ;;
    --auto)
      DO_AUTO=1; KEEP_DATA=1; DO_RUN=1; DO_TUNE=0; shift ;;
    --silent|--quiet|--from-app|--from-ui|--now|--force)
      # 3.3.7/3.3.8 có thể gửi flag này — bỏ qua, không die
      shift ;;
    --check|--print-version)
      DO_CHECK=1; shift ;;
    --no-timer)
      DO_TIMER=0; shift ;;
    --keep-data|--update)
      KEEP_DATA=1; shift ;;
    --wipe-data|--fresh|--clean)
      KEEP_DATA=0; shift ;;
    --no-systemd)
      DO_SYSTEMD=0; shift ;;
    --no-tune)
      DO_TUNE=0; shift ;;
    --no-node-install)
      SKIP_NODE_INSTALL=1; shift ;;
    -h|--help)
      usage; exit 0 ;;
    *)
      # Bản cũ có thể gửi flag lạ — không die (tránh update 3.3.7/3.3.8 chết giữa chừng)
      warn "Bỏ qua tham số lạ (compat): $1"
      shift ;;
  esac
done

# VERSION mặc định latest = stamp pack (WIPTER_LINUX_VERSION)
[ -n "$VERSION" ] || VERSION="latest"

if [ "$DO_CHECK" -eq 1 ]; then
  printf 'version=%s\n' "$WIPTER_LINUX_VERSION"
  printf 'run=%s\n' "$WIPTER_LINUX_RUN"
  printf 'url=%s/%s\n' "$BASE_URL" "$WIPTER_LINUX_RUN"
  printf 'feed=%s/wipter-setup.sh\n' "$BASE_URL"
  exit 0
fi

if [ "$DO_AUTO" -eq 1 ]; then
  KEEP_DATA=1
  DO_RUN=1
  DO_TUNE=0
fi

if [ -z "$INSTALL_DIR" ]; then
  # 3.3.7/3.3.8 đôi khi chỉ set env, không truyền -d
  if [ -n "${WIPTER_HOME:-}" ]; then
    INSTALL_DIR="$WIPTER_HOME"
  elif is_root; then
    INSTALL_DIR="/opt/wipter"
  else
    INSTALL_DIR="${HOME}/wipter"
  fi
fi

if [ -z "$SERVICE_USER" ]; then
  if is_root; then
    SERVICE_USER="root"
  else
    SERVICE_USER="$(id -un)"
  fi
fi

RUN_NAME="$(resolve_run_name "$VERSION")"
if [ "$VERSION" = "latest" ] || [ "$VERSION" = "current" ] || [ -z "$VERSION" ]; then
  VERSION="v${WIPTER_LINUX_VERSION}-linux"
fi
DOWNLOAD_URL="${BASE_URL}/${RUN_NAME}"
mkdir -p "$INSTALL_DIR"
INSTALL_DIR="$(cd "$INSTALL_DIR" && pwd)"

# Bản cũ spawn setup trong wipter.service — tách TRƯỚC download/stop
leave_wipter_cgroup_or_reexec
# Settings: unit cha timeout 90s — giao việc tải/extract sang unit dài rồi thoát
handoff_auto_long_job

log "Version      : $VERSION"
log "Download     : $DOWNLOAD_URL"
log "Install dir  : $INSTALL_DIR"
log "Service user : $SERVICE_USER"
log "Port         : $PORT"
log "Systemd      : $DO_SYSTEMD"
log "VPS tune     : $DO_TUNE"
log "Keep data    : $KEEP_DATA (1=giữ DB/runtime — mặc định, 0=cài sạch)"
if [ "$KEEP_DATA" -eq 0 ]; then
  warn "Đang dùng --wipe-data: data/ sẽ được ĐỔI TÊN sang data.bak-* (KHÔNG restore). Xác nhận bạn muốn cài sạch."
fi

need_cmd tar
need_cmd base64
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
  die "Cần curl hoặc wget"
fi

ensure_node

# 1) Tune VPS trước (để service mới hưởng DefaultLimitNOFILE)
if [ "$DO_TUNE" -eq 1 ]; then
  apply_vps_tune
fi

# 2) Download + extract
TMP_RUN="$(mktemp -t wipter-run-XXXXXX.run)"
# shellcheck disable=SC2064
trap 'rm -f "$TMP_RUN"' EXIT

CACHED_RUN="$INSTALL_DIR/$RUN_NAME"
if [ -f "$CACHED_RUN" ] && [ "$(wc -c <"$CACHED_RUN" | tr -d ' ')" -gt 20000000 ]; then
  log "Dùng $RUN_NAME đã tải ($(wc -c <"$CACHED_RUN" | tr -d ' ') bytes) — không tải lại"
  cp -f "$CACHED_RUN" "$TMP_RUN"
else
  log "Đang tải $RUN_NAME (service vẫn chạy)…"
  download "$DOWNLOAD_URL" "$TMP_RUN"
  ok "Đã tải ($(wc -c <"$TMP_RUN" | tr -d ' ') bytes)"
  cp -f "$TMP_RUN" "$CACHED_RUN"
  chmod +x "$CACHED_RUN" || true
fi

# Watchdog ngoài cgroup — start lại nếu updater chết sau extract
schedule_start_watchdog "$INSTALL_DIR"

# --auto không stop service (tránh giết curl/extract). Manual vẫn stop.
stop_running_install "$INSTALL_DIR"

log "Đang giải nén vào $INSTALL_DIR …"
extract_run_to "$TMP_RUN" "$INSTALL_DIR"
write_wrappers "$INSTALL_DIR" "$VERSION" "$RUN_NAME"
write_auto_update_helper "$INSTALL_DIR"
install_update_timer "$INSTALL_DIR"

# 3) systemd
SYSTEMD_OK=0
if [ "$DO_SYSTEMD" -eq 1 ]; then
  if install_systemd "$INSTALL_DIR" "$SERVICE_USER"; then
    SYSTEMD_OK=1
  fi
fi

ok "Cài đặt xong."
echo
if [ "$SYSTEMD_OK" -eq 1 ]; then
  printf '  Service     : systemctl status %s\n' "$SERVICE_NAME"
  printf '  Start       : systemctl start %s\n' "$SERVICE_NAME"
  printf '  Stop        : systemctl stop %s\n' "$SERVICE_NAME"
  printf '  Restart     : systemctl restart %s\n' "$SERVICE_NAME"
  printf '  Logs        : journalctl -u %s -f\n' "$SERVICE_NAME"
else
  printf '  Chạy tay    : %s\n' "$INSTALL_DIR/start.sh"
  printf '  Dừng        : %s\n' "$INSTALL_DIR/stop.sh"
fi
printf '  Web UI      : http://0.0.0.0:%s/web/\n' "$PORT"
echo

# 4) Start — luôn start lại (kể cả --auto). Không exec vào start.sh.
if [ "$DO_RUN" -eq 1 ]; then
  # Hàng đợi start ngoài cgroup — nếu restart giết updater, unit này vẫn bật service
  if [ "$DO_AUTO" -eq 1 ] && command -v systemd-run >/dev/null 2>&1; then
    systemctl reset-failed wipter-update-start.service >/dev/null 2>&1 || true
    systemd-run --no-block --unit=wipter-update-start --collect \
      --description='Wipter start after auto-update' \
      /bin/bash -c 'sleep 12; systemctl start wipter.service || systemctl start wipter || true' \
      >/dev/null 2>&1 || true
  fi
  log "Khởi động service ${SERVICE_NAME}…"
  ensure_wipter_started "$INSTALL_DIR" || true
  if command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet "${SERVICE_NAME}.service" 2>/dev/null; then
    systemctl --no-pager --full status "${SERVICE_NAME}.service" || true
  fi
elif [ "$SYSTEMD_OK" -eq 1 ]; then
  log "Service đã enable — sẽ chạy khi reboot. Start ngay:"
  printf '  systemctl start %s\n' "$SERVICE_NAME"
fi
