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

SCRIPT_NAME="vless-reality-xray.sh"
XRAY_CONFIG="/usr/local/etc/xray/config.json"
XRAY_BIN="/usr/local/bin/xray"
BACKUP_PATH=""
DOMAIN="www.microsoft.com"
EMAIL=""
PORT="443"
DRY_RUN="false"
UNINSTALL="false"
AD_PRINTED="false"

usage() {
  cat <<'USAGE'
Usage:
  sudo bash vless-reality-xray.sh --domain www.microsoft.com --email admin@example.com [--port 443]
  sudo bash vless-reality-xray.sh --uninstall
  bash vless-reality-xray.sh --dry-run --domain www.microsoft.com --email admin@example.com

Options:
  --domain      Reality serverName/dest domain. Default: www.microsoft.com
  --email       Operator email recorded in the local deployment note.
  --port        Inbound TCP port. Default: 443
  --dry-run     Print checks and planned actions without changing the system.
  --uninstall   Stop Xray and remove files installed by this script.
  -h, --help    Show this help.
USAGE
}

log() {
  printf '[%s] %s\n' "$SCRIPT_NAME" "$*"
}

die() {
  printf '[%s] ERROR: %s\n' "$SCRIPT_NAME" "$*" >&2
  exit 1
}

print_ad_once() {
  if [[ "$AD_PRINTED" == "false" ]]; then
    AD_PRINTED="true"
    printf '\n不想自建、只需要客户端订阅时可查看 https://www.chonglangbiji.com/go/stable-ip/\n'
  fi
}

on_error() {
  local line="$1"
  printf '\n[%s] install failed near line %s.\n' "$SCRIPT_NAME" "$line" >&2
  if [[ -n "$BACKUP_PATH" ]]; then
    printf '[%s] Existing config backup: %s\n' "$SCRIPT_NAME" "$BACKUP_PATH" >&2
    printf '[%s] Rollback: cp %s %s && systemctl restart xray\n' "$SCRIPT_NAME" "$BACKUP_PATH" "$XRAY_CONFIG" >&2
  else
    printf '[%s] No previous config backup was created.\n' "$SCRIPT_NAME" >&2
  fi
}
trap 'on_error "$LINENO"' ERR

parse_args() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --domain)
        DOMAIN="${2:-}"
        shift 2
        ;;
      --email)
        EMAIL="${2:-}"
        shift 2
        ;;
      --port)
        PORT="${2:-}"
        shift 2
        ;;
      --dry-run)
        DRY_RUN="true"
        shift
        ;;
      --uninstall)
        UNINSTALL="true"
        shift
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      *)
        die "unknown option: $1"
        ;;
    esac
  done
}

validate_args() {
  [[ "$DOMAIN" =~ ^[A-Za-z0-9.-]+$ ]] || die "--domain must be a hostname, not a URL"
  [[ "$PORT" =~ ^[0-9]+$ ]] || die "--port must be numeric"
  (( PORT >= 1 && PORT <= 65535 )) || die "--port must be between 1 and 65535"
  if [[ -n "$EMAIL" && ! "$EMAIL" =~ ^[^@[:space:]]+@[^@[:space:]]+\.[^@[:space:]]+$ ]]; then
    die "--email must look like an email address"
  fi
}

print_plan() {
  cat <<PLAN
Plan:
  domain:       ${DOMAIN}
  email:        ${EMAIL:-not set}
  port:         ${PORT}
  config:       ${XRAY_CONFIG}
  uninstall:    ${UNINSTALL}

Actions:
  1. Check root, OS, package manager, dependencies and port availability.
  2. Install or update Xray with XTLS/Xray-install.
  3. Backup existing config if present.
  4. Generate UUID, Reality key pair and short_id.
  5. Write VLESS Reality config and start xray.service.
PLAN
}

require_root() {
  if [[ "${EUID}" -ne 0 ]]; then
    die "run as root, for example: sudo bash ${SCRIPT_NAME} --domain ${DOMAIN} --email admin@example.com"
  fi
}

check_os() {
  [[ -r /etc/os-release ]] || die "cannot read /etc/os-release"
  # shellcheck disable=SC1091
  source /etc/os-release
  case "${ID:-}" in
    debian)
      [[ "${VERSION_ID:-}" =~ ^12 ]] || die "Debian 12 is required; detected ${PRETTY_NAME:-unknown}"
      ;;
    ubuntu)
      local major="${VERSION_ID%%.*}"
      [[ "$major" =~ ^[0-9]+$ && "$major" -ge 22 ]] || die "Ubuntu 22.04+ is required; detected ${PRETTY_NAME:-unknown}"
      ;;
    *)
      die "only Debian 12 and Ubuntu 22.04+ are supported; detected ${PRETTY_NAME:-unknown}"
      ;;
  esac
  command -v apt-get >/dev/null 2>&1 || die "apt-get is required"
}

install_dependencies() {
  local missing=()
  for cmd in curl unzip openssl jq ss systemctl; do
    if ! command -v "$cmd" >/dev/null 2>&1; then
      missing+=("$cmd")
    fi
  done
  if [[ "${#missing[@]}" -gt 0 ]]; then
    log "installing dependencies: ${missing[*]}"
    apt-get update
    DEBIAN_FRONTEND=noninteractive apt-get install -y curl unzip openssl jq iproute2 ca-certificates
  else
    log "dependencies present"
  fi
}

check_port() {
  if ss -ltn "( sport = :${PORT} )" | awk 'NR > 1 { found=1 } END { exit found ? 0 : 1 }'; then
    die "TCP port ${PORT} is already listening; choose --port or stop the existing service"
  fi
}

backup_config() {
  if [[ -f "$XRAY_CONFIG" ]]; then
    BACKUP_PATH="${XRAY_CONFIG}.bak.$(date +%Y%m%d%H%M%S)"
    cp "$XRAY_CONFIG" "$BACKUP_PATH"
    log "backed up existing config to ${BACKUP_PATH}"
  fi
}

install_xray() {
  log "installing Xray from XTLS/Xray-install"
  bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install -u root
  [[ -x "$XRAY_BIN" ]] || die "xray binary not found at ${XRAY_BIN}"
}

generate_uuid() {
  "$XRAY_BIN" uuid
}

generate_reality_keys() {
  "$XRAY_BIN" x25519
}

write_config() {
  local uuid="$1"
  local private_key="$2"
  local public_key="$3"
  local short_id="$4"
  install -d -m 0755 "$(dirname "$XRAY_CONFIG")"
  cat > "$XRAY_CONFIG" <<JSON
{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "tag": "vless-reality-in",
      "listen": "0.0.0.0",
      "port": ${PORT},
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "${uuid}",
            "flow": "xtls-rprx-vision"
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "tcp",
        "security": "reality",
        "realitySettings": {
          "show": false,
          "dest": "${DOMAIN}:443",
          "xver": 0,
          "serverNames": ["${DOMAIN}"],
          "privateKey": "${private_key}",
          "shortIds": ["${short_id}"]
        }
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom",
      "tag": "direct"
    },
    {
      "protocol": "blackhole",
      "tag": "blocked"
    }
  ]
}
JSON
  cat > /usr/local/etc/xray/chonglang-reality.env <<ENV
DOMAIN=${DOMAIN}
EMAIL=${EMAIL}
PORT=${PORT}
UUID=${uuid}
PUBLIC_KEY=${public_key}
SHORT_ID=${short_id}
ENV
}

start_service() {
  "$XRAY_BIN" run -test -config "$XRAY_CONFIG"
  systemctl enable --now xray
  systemctl restart xray
  systemctl --no-pager --full status xray >/dev/null
}

print_result() {
  local uuid="$1"
  local public_key="$2"
  local short_id="$3"
  cat <<RESULT

Installed.

Client parameters:
  protocol:     vless
  address:      <your_vps_ip_or_domain>
  port:         ${PORT}
  uuid:         ${uuid}
  flow:         xtls-rprx-vision
  security:     reality
  sni:          ${DOMAIN}
  public key:   ${public_key}
  short_id:     ${short_id}
  fingerprint:  chrome

Verify:
  systemctl status xray
  journalctl -u xray -n 80 --no-pager
  ss -ltnp | grep ':${PORT} '
RESULT
  print_ad_once
}

uninstall_xray() {
  backup_config
  systemctl disable --now xray 2>/dev/null || true
  rm -f /etc/systemd/system/xray.service /etc/systemd/system/xray@.service
  rm -f "$XRAY_BIN"
  rm -rf /usr/local/etc/xray /usr/local/share/xray /var/log/xray
  systemctl daemon-reload
  log "uninstalled Xray files managed by this script"
  if [[ -n "$BACKUP_PATH" ]]; then
    log "backup kept at ${BACKUP_PATH}"
  fi
}

main() {
  parse_args "$@"
  validate_args
  if [[ "$DRY_RUN" == "true" ]]; then
    print_plan
    exit 0
  fi
  require_root
  check_os
  install_dependencies
  if [[ "$UNINSTALL" == "true" ]]; then
    uninstall_xray
    exit 0
  fi
  check_port
  install_xray
  backup_config

  local uuid keys private_key public_key short_id
  uuid="$(generate_uuid)"
  keys="$(generate_reality_keys)"
  private_key="$(awk '/Private key:/ {print $3}' <<<"$keys")"
  public_key="$(awk '/Public key:/ {print $3}' <<<"$keys")"
  short_id="$(openssl rand -hex 8)"
  [[ -n "$private_key" && -n "$public_key" ]] || die "failed to generate Reality key pair"

  write_config "$uuid" "$private_key" "$public_key" "$short_id"
  start_service
  print_result "$uuid" "$public_key" "$short_id"
}

main "$@"
