#!/usr/bin/env bash
#
# Tailscale 安装配置 + 自建 DERP 中继服务 一键脚本
# 需要在目标 Linux 服务器上以 root 身份运行。
#
set -uo pipefail

GO_VERSION="1.26.4"
GO_TARBALL="go${GO_VERSION}.linux-amd64.tar.gz"
GO_URL="https://go.dev/dl/${GO_TARBALL}"
DERP_DIR="/etc/derp"
DERP_PORT="33445"
DERP_HTTP_PORT="33446"
STUN_PORT="3478"
PROXY_URL=""

# ---------- 输出辅助 ----------
c_reset="\033[0m"; c_red="\033[31m"; c_green="\033[32m"; c_yellow="\033[33m"; c_cyan="\033[36m"
log_info()  { echo -e "${c_cyan}[信息]${c_reset} $*"; }
log_ok()    { echo -e "${c_green}[完成]${c_reset} $*"; }
log_warn()  { echo -e "${c_yellow}[警告]${c_reset} $*"; }
log_err()   { echo -e "${c_red}[错误]${c_reset} $*"; }

confirm() {
    # confirm "提示语" [默认y|n]  返回 0 表示是
    local prompt="$1" default="${2:-n}" ans
    local hint="[y/N]"
    [ "$default" = "y" ] && hint="[Y/n]"
    read -r -p "$prompt $hint: " ans
    ans="${ans:-$default}"
    [[ "$ans" =~ ^[Yy]$ ]]
}

require_root() {
    if [ "$(id -u)" -ne 0 ]; then
        log_err "请以 root 身份运行本脚本（例如使用 sudo bash setup.sh）。"
        exit 1
    fi
}

# 统一下载函数：优先用 curl，没有 curl 时回退到 wget；两者都会带上代理（如果配置了的话）
download_file() {
    local url="$1" dest="$2"
    if command -v curl >/dev/null 2>&1; then
        if [ -n "$PROXY_URL" ]; then
            curl -fsSL -x "$PROXY_URL" -o "$dest" "$url"
        else
            curl -fsSL -o "$dest" "$url"
        fi
    elif command -v wget >/dev/null 2>&1; then
        if [ -n "$PROXY_URL" ]; then
            http_proxy="$PROXY_URL" https_proxy="$PROXY_URL" wget -q -O "$dest" "$url"
        else
            wget -q -O "$dest" "$url"
        fi
    else
        log_err "系统中未找到 curl 或 wget，无法下载文件。"
        return 1
    fi
}

# ---------- 步骤 0：下载代理设置 ----------
step_setup_proxy() {
    echo
    if confirm "下载 Tailscale 安装脚本 / Go 安装包时如果因网络原因卡顿或失败，可以配置 HTTP/HTTPS 代理来下载。是否需要配置代理？" n; then
        read -r -p "请输入代理地址（例如 http://127.0.0.1:7890）: " PROXY_URL
        if [ -z "$PROXY_URL" ]; then
            log_warn "未输入代理地址，将不使用代理。"
        else
            log_ok "已设置下载代理: ${PROXY_URL}"
            log_info "该代理仅用于 Tailscale 安装脚本与 Go 安装包的下载；探测公网 IP 时不会经过该代理，以免探测到代理出口 IP 而写错 DERP 配置。"
        fi
    fi
}

# ---------- 步骤 1：阿里云 / 冲突网段检测 ----------
step_check_aliyun() {
    echo
    log_info "开始前请确认：Tailscale 使用 100.64.0.0/10 网段作为虚拟内网。"
    if confirm "当前服务器是否为阿里云 ECS（或其它使用 100.64.0.0/10 网段对内提供 DNS 等服务的机器）？" n; then
        cat <<'EOF'

检测到你选择了"是"，脚本将在此终止，请你先手动处理以下事项：

1. 阿里云 ECS 默认使用 100.100.2.136 / 100.100.2.138 作为内网 DNS，
   这两个地址落在 Tailscale 使用的 100.64.0.0/10 网段内。
   请将 /etc/resolv.conf（或云厂商的网络配置）中的 DNS 改为其它可用 DNS，
   例如：223.5.5.5、114.114.114.114 或 8.8.8.8。

2. Tailscale 启动后默认会修改 iptables，把所有对外的 100.64.0.0/10 请求
   都 drop 掉，只允许走它自己建立的虚拟内网，这会导致依赖内网 100.64.0.0/10
   网段的服务（例如阿里云内网 DNS/NTP 等）出现故障。
   建议在启动 Tailscale 时加上参数关闭它对 iptables 的接管，例如：
       tailscale up --netfilter-mode=off
   或者自行编写 iptables 规则放行你需要的 100.64.0.0/10 内网地址。

处理完成后，请重新运行本脚本，并在此处选择"否"以继续后续安装。

EOF
        exit 0
    fi
    log_info "已选择「否」，继续执行安装流程。"
}

# ---------- 步骤 2：安装 Tailscale ----------
step_install_tailscale() {
    echo
    if command -v tailscale >/dev/null 2>&1; then
        log_ok "检测到 Tailscale 已安装（$(tailscale version | head -n1)），跳过安装步骤。"
    else
        log_info "正在安装 Tailscale 客户端..."
        local install_ok=1
        if [ -n "$PROXY_URL" ]; then
            curl -fsSL -x "$PROXY_URL" https://tailscale.com/install.sh | sh || install_ok=0
        else
            curl -fsSL https://tailscale.com/install.sh | sh || install_ok=0
        fi
        if [ "$install_ok" -ne 1 ]; then
            log_err "Tailscale 安装失败，请检查网络（或代理设置）后重试。"
            exit 1
        fi
        log_ok "Tailscale 安装完成。"
    fi
}

# ---------- 步骤 3：参数菜单并启动 ----------
step_configure_and_up() {
    echo
    cat <<'EOF'
请选择启动 Tailscale 时要附加的参数（可多选，用空格或逗号分隔编号，例如: 1 3）：

  编号  参数                      作用
  1     --advertise-exit-node     宣告出口节点
  2     --advertise-routes=       宣告子网
  3     --accept-routes           接受其它节点发布的路由（默认）
  4     --accept-dns              接受 MagicDNS 配置
  5     --exit-node=              指定出口节点
  6     --hostname=               修改节点名称
  7     --reset                   重置所有配置（会清除已有设置，请谨慎叠加）

EOF
    read -r -p "请输入编号 [默认: 3]: " choices
    choices="${choices:-3}"
    choices="${choices//,/ }"

    local -a ts_args=()
    local selected_routes="" selected_exit_node="" selected_hostname=""

    for n in $choices; do
        case "$n" in
            1)
                ts_args+=("--advertise-exit-node")
                enable_ip_forwarding
                ;;
            2)
                read -r -p "请输入要宣告的子网（逗号分隔，格式如 192.168.1.0/24,192.168.50.0/24）: " selected_routes
                ts_args+=("--advertise-routes=${selected_routes}")
                ;;
            3) ts_args+=("--accept-routes") ;;
            4) ts_args+=("--accept-dns") ;;
            5)
                read -r -p "请输入要指定的出口节点（IP 或节点名）: " selected_exit_node
                ts_args+=("--exit-node=${selected_exit_node}")
                ;;
            6)
                read -r -p "请输入新的节点名称: " selected_hostname
                ts_args+=("--hostname=${selected_hostname}")
                ;;
            7) ts_args+=("--reset") ;;
            *) log_warn "忽略无法识别的编号: $n" ;;
        esac
    done

    log_info "即将执行: tailscale up ${ts_args[*]}"
    log_info "若为首次认证，下方会出现认证网址，请复制到浏览器完成登录授权。"
    tailscale up "${ts_args[@]}"
    log_ok "Tailscale 启动命令已执行。"
}

enable_ip_forwarding() {
    local changed=0
    if [ "$(sysctl -n net.ipv4.ip_forward 2>/dev/null)" != "1" ]; then
        if grep -q '^net\.ipv4\.ip_forward' /etc/sysctl.conf 2>/dev/null; then
            sed -i 's/^net\.ipv4\.ip_forward.*/net.ipv4.ip_forward = 1/' /etc/sysctl.conf
        else
            echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
        fi
        changed=1
    fi
    if [ "$(sysctl -n net.ipv6.conf.all.forwarding 2>/dev/null)" != "1" ]; then
        if grep -q '^net\.ipv6\.conf\.all\.forwarding' /etc/sysctl.conf 2>/dev/null; then
            sed -i 's/^net\.ipv6\.conf\.all\.forwarding.*/net.ipv6.conf.all.forwarding = 1/' /etc/sysctl.conf
        else
            echo 'net.ipv6.conf.all.forwarding = 1' >> /etc/sysctl.conf
        fi
        changed=1
    fi
    if [ "$changed" -eq 1 ]; then
        sysctl -p >/dev/null 2>&1
        log_ok "已开启内核转发（ip_forward），用于出口节点功能。"
    else
        log_ok "内核转发已开启，无需修改。"
    fi
}

# ---------- 步骤 4：是否继续部署 DERP ----------
step_ask_continue_derp() {
    echo
    if ! confirm "Tailscale 基础配置已完成。是否继续部署自建 DERP 中继服务？普通用户可直接结束" n; then
        log_ok "脚本结束。"
        exit 0
    fi
}

# ---------- 步骤 5：安装 Go ----------
step_install_go() {
    echo
    local arch
    arch="$(uname -m)"
    if [ "$arch" != "x86_64" ]; then
        log_warn "检测到当前架构为 ${arch}，本脚本固定下载的是 linux-amd64 版本 Go，可能与当前架构不匹配。"
        if ! confirm "是否仍然继续（可能导致 Go 无法运行）？" n; then
            log_err "已取消。请自行前往 https://go.dev/dl/ 下载对应架构的 Go 后重新运行脚本。"
            exit 1
        fi
    fi

    if command -v go >/dev/null 2>&1 && [ -x /usr/local/go/bin/go ]; then
        log_ok "检测到 Go 已安装（$(go version)），跳过下载安装步骤。"
    else
        log_info "正在下载 Go ${GO_VERSION}...$([ -n "$PROXY_URL" ] && echo " (通过代理 ${PROXY_URL})")"
        local tmp_tar="/tmp/${GO_TARBALL}"
        if ! download_file "$GO_URL" "$tmp_tar"; then
            log_err "Go 下载失败，请检查网络/代理设置，或确认版本号是否仍然可用: $GO_URL"
            exit 1
        fi
        rm -rf /usr/local/go
        tar -C /usr/local -xzf "$tmp_tar"
        rm -f "$tmp_tar"
        log_ok "Go 解压完成。"
    fi

    if ! grep -q '/usr/local/go/bin' /etc/profile 2>/dev/null; then
        echo 'export PATH=$PATH:/usr/local/go/bin' >> /etc/profile
    fi
    export PATH="$PATH:/usr/local/go/bin"

    go env -w GO111MODULE=on
    go env -w GOPROXY=https://goproxy.cn,direct
    log_ok "Go 环境变量与国内代理配置完成。"
}

# ---------- 步骤 6：编译 derper ----------
step_build_derper() {
    echo
    if [ -x "${DERP_DIR}/derper" ]; then
        log_ok "检测到 ${DERP_DIR}/derper 已存在，跳过下载编译步骤。"
        return
    fi

    log_info "正在拉取并编译 derper（go install），这一步会联网下载源码，可能需要一些时间..."
    if ! go install tailscale.com/cmd/derper@main; then
        log_err "derper 拉取编译失败，请检查网络或 GOPROXY 设置。"
        exit 1
    fi

    local gopath mod_dir derper_dir
    gopath="$(go env GOPATH)"
    mod_dir="${gopath}/pkg/mod"
    derper_dir="$(ls -d "${mod_dir}"/tailscale.com@*/cmd/derper 2>/dev/null | sort -V | tail -n1)"
    if [ -z "$derper_dir" ] || [ ! -d "$derper_dir" ]; then
        log_err "未能在 ${mod_dir} 下找到 tailscale.com@*/cmd/derper 目录，请手动确认。"
        exit 1
    fi
    log_info "定位到源码目录: $derper_dir"

    # Go module 缓存默认只读，需要先加上写权限才能修改 cert.go
    chmod -R u+w "$(dirname "$(dirname "$derper_dir")")"

    local cert_file="${derper_dir}/cert.go"
    local line_no
    line_no="$(grep -n 'if hi.ServerName != m.hostname {' "$cert_file" | head -n1 | cut -d: -f1)"
    if [ -z "$line_no" ]; then
        log_warn "cert.go 中未找到需要注释的目标代码（可能上游代码已变化），请手动检查: $cert_file"
    else
        local l1=$((line_no)) l2=$((line_no + 1)) l3=$((line_no + 2))
        sed -i "${l1}s#^#// #; ${l2}s#^#// #; ${l3}s#^#// #" "$cert_file"
        if sed -n "${l1}p" "$cert_file" | grep -q '^// ' \
           && sed -n "${l2}p" "$cert_file" | grep -q '^// ' \
           && sed -n "${l3}p" "$cert_file" | grep -q '^// '; then
            log_ok "cert.go 已完成注释修改并校验通过。"
        else
            log_err "cert.go 校验失败，注释可能未正确写入，请手动检查: $cert_file"
            exit 1
        fi
    fi

    mkdir -p "$DERP_DIR"
    log_info "正在编译 derper 到 ${DERP_DIR}/derper ..."
    if ! (cd "$derper_dir" && go build -o "${DERP_DIR}/derper"); then
        log_err "derper 编译失败。"
        exit 1
    fi

    if [ -x "${DERP_DIR}/derper" ]; then
        log_ok "derper 编译成功: ${DERP_DIR}/derper"
    else
        log_err "编译似乎已完成，但未在 ${DERP_DIR} 找到 derper 文件，请手动检查。"
        exit 1
    fi
}

# ---------- 步骤 7：生成自签证书 ----------
DOMAIN=""
step_generate_cert() {
    echo
    read -r -p "请输入用于 DERP 的域名（可随意编写，例如 derp.myself.com）: " DOMAIN
    if [ -z "$DOMAIN" ]; then
        log_err "域名不能为空。"
        exit 1
    fi

    local key_file="${DERP_DIR}/${DOMAIN}.key"
    local crt_file="${DERP_DIR}/${DOMAIN}.crt"
    if [ -f "$key_file" ] && [ -f "$crt_file" ]; then
        if ! confirm "检测到 ${DOMAIN} 的证书已存在，是否重新生成（旧证书将被覆盖）？" n; then
            log_ok "保留现有证书。"
            return
        fi
    fi

    openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
        -keyout "$key_file" -out "$crt_file" \
        -subj "/CN=${DOMAIN}" -addext "subjectAltName=DNS:${DOMAIN}"
    log_ok "自签证书已生成: $crt_file"
}

# ---------- 步骤 8：systemd 服务 ----------
step_setup_systemd() {
    echo
    cat > /etc/systemd/system/derp.service <<EOF
[Unit]
Description=TS Derper
After=network.target
Wants=network.target
[Service]
User=root
Restart=always
ExecStart=${DERP_DIR}/derper -hostname ${DOMAIN} -a :${DERP_PORT} -http-port ${DERP_HTTP_PORT} -verify-clients -certmode manual -certdir ${DERP_DIR}
RestartPreventExitStatus=1
[Install]
WantedBy=multi-user.target
EOF

    systemctl daemon-reload
    systemctl enable derp >/dev/null 2>&1
    systemctl restart derp

    sleep 1
    if systemctl is-active --quiet derp; then
        log_ok "derp 服务已启动并设置开机自启。"
    else
        log_err "derp 服务启动失败，请执行 systemctl status derp 查看详情。"
        exit 1
    fi
}

# ---------- 步骤 9：结尾提示 ----------
step_final_instructions() {
    echo
    log_warn "请记得在防火墙/云服务器安全组中放行: ${DERP_PORT}/TCP 和 ${STUN_PORT}/UDP"

    local public_ip
    public_ip="$(curl -fsSL4 --max-time 5 ifconfig.me 2>/dev/null || curl -fsSL4 --max-time 5 https://api.ipify.org 2>/dev/null)"
    if [ -z "$public_ip" ]; then
        log_warn "未能自动探测到公网 IP，请自行确认后替换下方模板中的 IP 地址。"
        public_ip="<你的公网IP>"
    fi

    cat <<EOF

测试方法：访问 https://${public_ip}:${DERP_PORT} ，若地址栏 SSL 证书标签显示正常可用，
则说明 DERP 中继服务部署成功。若失败，请检查域名解析、证书配置以及
${DERP_PORT}/TCP、${STUN_PORT}/UDP 端口在安全组/防火墙中是否已放行。

------------------------------------------------------------
以下内容不由本脚本自动执行，需要你自行前往 Tailscale 后台完成：
https://login.tailscale.com/admin/acls/file

将以下内容追加到 Access controls 配置文件中并保存：

// Tailscale DERP
"derpMap": {
    "OmitDefaultRegions": true,
    "Regions": {
        "901": {
            "RegionID":   901,
            "RegionCode": "sh",
            "RegionName": "Shanghai",
            "Nodes": [
                {
                    "Name":             "自建节点-1",
                    "RegionID":         901,
                    "IPv4":             "${public_ip}",
                    "DERPPort":         ${DERP_PORT},
                    "InsecureForTests": true
                }
            ]
        }
    }
},
------------------------------------------------------------

EOF
}

main() {
    require_root
    step_setup_proxy
    step_check_aliyun
    step_install_tailscale
    step_configure_and_up
    step_ask_continue_derp
    step_install_go
    step_build_derper
    step_generate_cert
    step_setup_systemd
    step_final_instructions
}

main "$@"
