#!/bin/bash
# node-agent 节点装机（M5：steal-yourself sslip.io + nginx 伪装站 + ZeroSSL 证书 + 多内核）
# 演进自验证版 v0：
#   - reality serverName/dest 改 {public_ip}.sslip.io，dest 回落本机 nginx:8443 伪装站（steal-yourself）
#   - nginx:8443 持 {ip}.sslip.io 的 ZeroSSL 公信证书（acme.sh，LE 兜底），root=/var/www/decoy
#   - 二进制(xray/hysteria)与 geoip 从 RESOURCE_BASE 拉取（版本对齐 agent 依赖）
#   - 给 hysteria 下发 geoip.dat（agent 开「大陆IP开关」时写 acl.geoip 用）
#   - 伪装站素材由 agent 启动后从 RESOURCE_BASE 拉取并解压到 /var/www/decoy（此处只放占位页）
set -e
log(){ echo "[$(date '+%H:%M:%S')] $1"; }

RESOURCE_BASE="${RESOURCE_BASE:-https://64.118.149.202.sslip.io}"
ACME_EMAIL="${ACME_EMAIL:-niubdaidai@gmail.com}"
DECOY_ROOT="/var/www/decoy"

PUBLIC_IP=$(curl -s4 --max-time 10 ifconfig.me || curl -s4 --max-time 10 ip.sb)
[ -z "$PUBLIC_IP" ] && { echo "FATAL: no public ip"; exit 1; }
DECOY_DOMAIN="${PUBLIC_IP}.sslip.io"
log "public ip: $PUBLIC_IP   decoy domain: $DECOY_DOMAIN   resource: $RESOURCE_BASE"

export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq unzip curl openssl nginx cron socat python3 >/dev/null 2>&1
systemctl stop nginx 2>/dev/null || true   # 释放 apt 默认占用的 :80，给 acme standalone 用
systemctl stop xray hysteria 2>/dev/null || true   # 重装时先停内核，避免 curl 覆盖运行中二进制报 ETXTBSY
log "deps installed (nginx/cron/socat/...)"

# ========== 内核网络调优 + BBR（VPN 代理：高并发 / 大吞吐 / QUIC-UDP 缓冲）==========
log "tune kernel: BBR + 网络缓冲/队列"
modprobe tcp_bbr 2>/dev/null || true
echo tcp_bbr > /etc/modules-load.d/bbr.conf   # 重启后自动加载 bbr 模块
# 仅当内核确实提供 bbr 才切拥塞算法，否则保持默认（避免老内核 sysctl 失败/无 BBR 时误配）
if grep -qw bbr /proc/sys/net/ipv4/tcp_available_congestion_control 2>/dev/null; then
  BBR_CC="net.ipv4.tcp_congestion_control = bbr"
  log "BBR available -> enable"
else
  BBR_CC="# net.ipv4.tcp_congestion_control = bbr   # 内核不支持，已跳过"
  log "WARN: 内核未提供 BBR，跳过拥塞算法切换（其余调优照常）"
fi
cat > /etc/sysctl.d/99-node-agent.conf <<EOF
# node-agent 节点调优（VPN 代理：高并发 + 大吞吐 + QUIC/UDP）。由 setup-node.sh 生成。
# —— 拥塞控制：BBR + fq（高带宽低延迟）——
net.core.default_qdisc = fq
${BBR_CC}
# —— 文件描述符（海量并发连接）——
fs.file-max = 1048576
# —— 连接队列（抗突发 / 抗 SYN flood）——
net.core.somaxconn = 32768
net.core.netdev_max_backlog = 32768
net.ipv4.tcp_max_syn_backlog = 32768
# —— TCP 缓冲区（长肥管道吞吐）——
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
# —— UDP 缓冲区（hysteria2/QUIC 必需：缓冲过小会告警 receive buffer 并丢包）——
net.core.rmem_default = 2500000
net.core.wmem_default = 2500000
# —— 连接回收/复用（短连代理）——
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_tw_buckets = 262144
net.ipv4.tcp_slow_start_after_idle = 0
# —— Fast Open + MTU 探测 + keepalive ——
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_keepalive_time = 600
EOF
sysctl --system >/dev/null 2>&1 || true
log "sysctl 生效: cc=$(cat /proc/sys/net/ipv4/tcp_congestion_control 2>/dev/null) qdisc=$(cat /proc/sys/net/core/default_qdisc 2>/dev/null)"

mkdir -p /usr/local/bin /usr/local/etc/xray /usr/local/share/xray /etc/hysteria \
         /etc/node-agent/ssl "$DECOY_ROOT" /var/lib/node-agent

# ========== 二进制 + geoip（从 RESOURCE_BASE 拉，版本固定）==========
log "fetch xray / hysteria / geoip from RESOURCE_BASE"
curl -fsSL -o /usr/local/bin/xray     "${RESOURCE_BASE}/bin/xray"     && chmod +x /usr/local/bin/xray
curl -fsSL -o /usr/local/bin/hysteria "${RESOURCE_BASE}/bin/hysteria" && chmod +x /usr/local/bin/hysteria
curl -fsSL -o /usr/local/share/xray/geoip.dat   "${RESOURCE_BASE}/geo/geoip.dat"
curl -fsSL -o /usr/local/share/xray/geosite.dat "${RESOURCE_BASE}/geo/geosite.dat"
cp /usr/local/share/xray/geoip.dat /etc/hysteria/geoip.dat   # hysteria acl 的 geoip 来源
log "xray:     $(/usr/local/bin/xray version | head -1)"
log "hysteria: $(/usr/local/bin/hysteria version 2>/dev/null | head -1)"

# reality 密钥对（宽松解析：x25519 输出格式各版本不同，抓 base64url 串）
KP=$(/usr/local/bin/xray x25519)
RPVK=$(echo "$KP" | grep -iE 'private' | grep -oE '[A-Za-z0-9_-]{42,}' | head -1)
RPBK=$(echo "$KP" | grep -iE 'public|password' | grep -oE '[A-Za-z0-9_-]{42,}' | head -1)
RSID=$(openssl rand -hex 8)
[ -z "$RPVK" ] || [ -z "$RPBK" ] && { echo "FATAL: reality keypair parse failed:"; echo "$KP"; exit 1; }
log "reality pbk=$RPBK sid=$RSID"

# ========== ZeroSSL 证书（{ip}.sslip.io，acme.sh standalone:80，LE 兜底）==========
log "issue cert for $DECOY_DOMAIN (ZeroSSL primary, LE fallback)"
if [ ! -f ~/.acme.sh/acme.sh ]; then
  curl -fsSL https://get.acme.sh | sh -s email="$ACME_EMAIL" >/dev/null 2>&1
fi
ACME=~/.acme.sh/acme.sh
$ACME --set-default-ca --server zerossl >/dev/null 2>&1 || true
$ACME --register-account -m "$ACME_EMAIL" >/dev/null 2>&1 || true
fuser -k 80/tcp >/dev/null 2>&1 || true
if [ ! -f ~/.acme.sh/${DECOY_DOMAIN}_ecc/fullchain.cer ]; then
  if ! $ACME --issue -d "$DECOY_DOMAIN" --standalone --keylength ec-256 --server zerossl; then
    log "ZeroSSL failed, fallback to Let's Encrypt"
    $ACME --issue -d "$DECOY_DOMAIN" --standalone --keylength ec-256 --server letsencrypt
  fi
fi
# 安装证书到固定路径；reloadcmd 仅为日后续期触发，此刻 nginx 尚未起→失败容忍（证书文件在 reload 前已写好）。
$ACME --install-cert -d "$DECOY_DOMAIN" --ecc \
  --key-file       /etc/node-agent/ssl/key.pem \
  --fullchain-file /etc/node-agent/ssl/fullchain.pem \
  --reloadcmd      "systemctl reload nginx" >/dev/null 2>&1 || true
[ -s /etc/node-agent/ssl/fullchain.pem ] || { echo "FATAL: cert not installed"; exit 1; }
log "cert installed -> /etc/node-agent/ssl/"

# ========== nginx 伪装站（127.0.0.1:8443，reality dest 回落目标）==========
cat > "$DECOY_ROOT/index.html" <<'HTML'
<!doctype html><html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"><title>Welcome</title></head>
<body style="font-family:system-ui;margin:3rem auto;max-width:40rem"><h1>It works!</h1>
<p>This page is served by the default web server. Site content is being provisioned.</p></body></html>
HTML
cat > /etc/nginx/sites-available/decoy <<EOF
server {
    listen 127.0.0.1:8443 ssl default_server;
    server_name ${DECOY_DOMAIN};
    ssl_certificate     /etc/node-agent/ssl/fullchain.pem;
    ssl_certificate_key /etc/node-agent/ssl/key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    root ${DECOY_ROOT};
    index index.html;
    location / { try_files \$uri \$uri/ =404; }
}
EOF
ln -sf /etc/nginx/sites-available/decoy /etc/nginx/sites-enabled/decoy
rm -f /etc/nginx/sites-enabled/default
# 给 api 用的 HTTPS 反代：对外 10443 → 本机 agent 契约端口 8080（api 强制 https 且信任自签证书）。
cat > /etc/nginx/sites-available/agent-api <<EOF
server {
    listen 0.0.0.0:10443 ssl;
    server_name ${DECOY_DOMAIN};
    ssl_certificate     /etc/node-agent/ssl/fullchain.pem;
    ssl_certificate_key /etc/node-agent/ssl/key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    client_max_body_size 4m;
    location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host \$host; proxy_read_timeout 600s; proxy_send_timeout 600s; }
}
EOF
ln -sf /etc/nginx/sites-available/agent-api /etc/nginx/sites-enabled/agent-api
nginx -t
systemctl enable nginx >/dev/null 2>&1 || true
systemctl restart nginx
log "nginx: $(systemctl is-active nginx) (127.0.0.1:8443)"

# ========== Xray config（reality steal-yourself + 回落 nginx:8443）==========
# routing 只放 api 规则；大陆IP开关由 agent 运行时改写（加 geoip:cn→blocked + domainStrategy）。
cat > /usr/local/etc/xray/config.json <<EOF
{
  "log": { "loglevel": "warning" },
  "api": { "tag": "api", "services": ["HandlerService", "StatsService"] },
  "stats": {},
  "policy": {
    "levels": { "0": { "statsUserUplink": true, "statsUserDownlink": true } },
    "system": { "statsInboundUplink": true, "statsInboundDownlink": true }
  },
  "inbounds": [
    {
      "tag": "vless-reality", "listen": "0.0.0.0", "port": 443, "protocol": "vless",
      "settings": { "clients": [], "decryption": "none" },
      "streamSettings": {
        "network": "tcp", "security": "reality",
        "realitySettings": {
          "show": false,
          "dest": "127.0.0.1:8443",
          "serverNames": ["${DECOY_DOMAIN}"],
          "privateKey": "${RPVK}",
          "shortIds": ["${RSID}"]
        }
      }
    },
    {
      "tag": "api", "listen": "127.0.0.1", "port": 10085, "protocol": "dokodemo-door",
      "settings": { "address": "127.0.0.1" }
    }
  ],
  "outbounds": [ { "protocol": "freedom", "tag": "direct" } ],
  "routing": { "rules": [ { "type": "field", "inboundTag": ["api"], "outboundTag": "api" } ] }
}
EOF

cat > /etc/systemd/system/xray.service <<'EOF'
[Unit]
Description=Xray
After=network.target nss-lookup.target
[Service]
ExecStart=/usr/local/bin/xray run -c /usr/local/etc/xray/config.json
Restart=on-failure
RestartSec=3
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
EOF

# ========== hysteria（auth http→agent + trafficStats；acl 由 agent 运行时改写）==========
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
  -keyout /etc/node-agent/hy2.key -out /etc/node-agent/hy2.crt -days 3650 \
  -subj "/CN=${DECOY_DOMAIN}" >/dev/null 2>&1
HY2PORT=$(( RANDOM % 20000 + 30000 ))
HY2OBFS=$(openssl rand -hex 8)
STATSSECRET=$(openssl rand -hex 16)
cat > /etc/hysteria/config.yaml <<EOF
listen: :${HY2PORT}
tls:
  cert: /etc/node-agent/hy2.crt
  key: /etc/node-agent/hy2.key
auth:
  type: http
  http:
    url: http://127.0.0.1:9090/auth
trafficStats:
  listen: 127.0.0.1:9999
  secret: ${STATSSECRET}
obfs:
  type: salamander
  salamander:
    password: ${HY2OBFS}
EOF

cat > /etc/systemd/system/hysteria.service <<'EOF'
[Unit]
Description=Hysteria2
After=network.target
[Service]
ExecStart=/usr/local/bin/hysteria server -c /etc/hysteria/config.yaml
Restart=on-failure
RestartSec=3
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
EOF

# ========== 防火墙（best-effort）==========
if command -v ufw >/dev/null 2>&1; then
  ufw allow 443/tcp        >/dev/null 2>&1 || true
  ufw allow ${HY2PORT}/udp >/dev/null 2>&1 || true
  ufw allow 80/tcp         >/dev/null 2>&1 || true   # acme.sh 续期 standalone 用
  ufw allow 10443/tcp      >/dev/null 2>&1 || true   # api → agent 契约 HTTPS 反代
  ufw allow 22/tcp         >/dev/null 2>&1 || true
fi

# ========== 启动两内核 ==========
systemctl daemon-reload
systemctl enable --now xray
systemctl enable --now hysteria
sleep 2

# ========== 参数落盘，供 deploy-agent.sh 生成 config.yaml ==========
cat > /etc/node-agent/node.env <<EOF
PUBLIC_IP=${PUBLIC_IP}
DECOY_DOMAIN=${DECOY_DOMAIN}
REALITY_PBK=${RPBK}
REALITY_SID=${RSID}
HY2_PORT=${HY2PORT}
HY2_OBFS=${HY2OBFS}
STATS_SECRET=${STATSSECRET}
RESOURCE_BASE=${RESOURCE_BASE}
DECOY_ROOT=${DECOY_ROOT}
EOF

# ── 安装 host 自更新（节点 IP 变更时自动重签 {ip}.sslip.io 证书 + 改 nginx/xray/agent + 重启 + 上报 api）──
if curl -fsSL -o /tmp/node-host-update.sh "${RESOURCE_BASE}/host-update.sh"; then
  ACME_EMAIL="$ACME_EMAIL" AGENT_API_PORT=10443 bash /tmp/node-host-update.sh \
    && log "host-update 已安装（cron 每小时检查 IP）" || log "WARN: host-update 安装失败"
else
  log "WARN: 未能从 RESOURCE_BASE 拉取 host-update.sh，跳过 host 自更新安装"
fi

echo "==== RESULT ===="
cat /etc/node-agent/node.env
echo "nginx:    $(systemctl is-active nginx)"
echo "xray:     $(systemctl is-active xray)"
echo "hysteria: $(systemctl is-active hysteria)"
echo "--- listen ---"
ss -tlnp 2>/dev/null | grep -E ':(443|8443) ' || echo "(no 443/8443 tcp!)"
ss -ulnp 2>/dev/null | grep ":${HY2PORT} " || echo "(no hy2 udp!)"
echo "--- decoy fallback self-check (reality dest = nginx) ---"
curl -fsS --resolve "${DECOY_DOMAIN}:8443:127.0.0.1" "https://${DECOY_DOMAIN}:8443/" -o /dev/null \
  -w "nginx decoy HTTP %{http_code}\n" || echo "(nginx decoy check failed)"
echo "==== DONE (next: deploy-agent.sh) ===="
