#!/bin/sh
#
# Larktun 一键安装脚本（OpenWrt 21.02 / 22.03 / 23.05 / 24.10，opkg/ipk）
#
# 在 OpenWrt 设备上执行：
#   wget -O /tmp/larktun.sh https://download.larktun.com/openwrt/install.sh && sh /tmp/larktun.sh
#
# 全自动入网（无需人工点登录链接）：先在 Tailscale 后台生成 auth key，再：
#   LARKTUN_AUTHKEY=tskey-xxxx sh /tmp/larktun.sh
#
# 可选环境变量：
#   LARKTUN_AUTHKEY   提供后用 authkey 自动登录入网
#   LARKTUN_HOSTNAME  设备在网络中的名字，如 my-openwrt
#   LARKTUN_UP_ARGS   追加给 `larktun up` 的参数，如 "--advertise-routes=10.0.0.0/24 --advertise-exit-node"
#
set -e

# ===================== 配置 =====================
PKG_VERSION="1.86.2"
# 两个下载站，按顺序尝试
MIRRORS="https://download2.larktun.com https://download.larktun.com"
# 包与脚本放在下载站的 /openwrt 子目录下
PATH_PREFIX="/openwrt"
# 设备 OpenWrt 系列 -> 已发布补丁版本（发新包后更新这里即可）
map_series_to_pub() {
  case "$1" in
    24.10) echo "24.10.4" ;;
    23.05) echo "23.05.5" ;;
    22.03) echo "22.03.7" ;;
    21.02) echo "21.02.7" ;;
    *)     echo "" ;;
  esac
}
# 候选架构：先精确匹配，再回退到同 CPU 家族已发布的包。
# larktund 是静态链接二进制，同家族(如各 aarch64)可通用，非精确时用 --force-architecture 安装。
arch_candidates() {
  case "$1" in
    aarch64*) echo "$1 aarch64_cortex-a72 aarch64_cortex-a53 aarch64_generic" ;;
    x86_64)   echo "x86_64" ;;
    *)        echo "$1" ;;
  esac
}
# ================================================

AUTHKEY="${LARKTUN_AUTHKEY:-}"
UP_ARGS="${LARKTUN_UP_ARGS:-}"
HOSTNAME_ARG=""
[ -n "${LARKTUN_HOSTNAME:-}" ] && HOSTNAME_ARG="--hostname=${LARKTUN_HOSTNAME}" || true

log() { echo "[INFO]  $*"; }
err() { echo "[ERROR] $*" >&2; }
die() { err "$*"; exit 1; }

[ "$(id -u)" = "0" ] || die "请用 root 运行本脚本。"
command -v opkg >/dev/null 2>&1 || die "未找到 opkg。本脚本面向 OpenWrt 24.10 及更早(ipk)；25.12+(apk) 暂未提供包。"

# 1. 识别架构
ARCH=$(opkg print-architecture | awk 'NF==3 && $3 ~ /^[0-9]+$/ {print $2}' | grep -vE '^(all|noarch)$' | tail -1)
[ -n "$ARCH" ] || die "无法识别设备架构。"
log "架构: $ARCH"

# 2. 识别 OpenWrt 版本 -> 系列 -> 发布版本
REL=""
[ -f /etc/openwrt_release ] && REL=$( . /etc/openwrt_release 2>/dev/null; echo "$DISTRIB_RELEASE" ) || true
[ -z "$REL" ] && [ -f /etc/os-release ] && REL=$( . /etc/os-release 2>/dev/null; echo "$VERSION_ID" ) || true
[ -n "$REL" ] || die "无法识别 OpenWrt 版本。"
# 提取前导 主.次 版本，兼容 21.02.7 / 21.02-SNAPSHOT / 23.05-rc1 等写法
SERIES=$(echo "$REL" | grep -oE '^[0-9]+\.[0-9]+' | head -1)
PUB=$(map_series_to_pub "$SERIES")
log "OpenWrt: $REL (系列 ${SERIES:-未知})"
[ -n "$PUB" ] || die "暂未为 OpenWrt ${SERIES:-$REL} 提供 larktun 包（已支持 21.02 / 22.03 / 23.05 / 24.10）。"

# 3. 下载器：多工具 + 多镜像；TLS 失败时回退跳过证书校验（靠 sha256 保完整性）
fetch() { # $1=url $2=out
  _u="$1"; _o="$2"
  if command -v uclient-fetch >/dev/null 2>&1; then
    uclient-fetch -qO "$_o" "$_u" && return 0
    uclient-fetch -q --no-check-certificate -O "$_o" "$_u" && { echo "[WARN] 已跳过证书校验" >&2; return 0; }
  fi
  if command -v wget >/dev/null 2>&1; then
    wget -qO "$_o" "$_u" && return 0
    wget -q --no-check-certificate -O "$_o" "$_u" 2>/dev/null && { echo "[WARN] 已跳过证书校验" >&2; return 0; }
  fi
  if command -v curl >/dev/null 2>&1; then
    curl -fsSL -o "$_o" "$_u" && return 0
    curl -fsSLk -o "$_o" "$_u" && { echo "[WARN] 已跳过证书校验" >&2; return 0; }
  fi
  return 1
}

# 校验文件是否为有效 ipk（gzip 魔数 1f 8b），用于排除"200+HTML 错误页"伪装
is_gzip() {
  command -v od >/dev/null 2>&1 || return 0   # 无 od 时不阻拦
  [ "$(od -An -N2 -tx1 "$1" 2>/dev/null | tr -d ' \n')" = "1f8b" ]
}

download_mirrors() { # $1=relative_file $2=out $3=validate(gzip|空)
  _rel="$1"; _out="$2"; _val="${3:-}"
  for base in $MIRRORS; do
    _url="${base}${PATH_PREFIX}/${_rel}"
    log "下载: $_url"
    if fetch "$_url" "$_out" && [ -s "$_out" ]; then
      if [ "$_val" = "gzip" ] && ! is_gzip "$_out"; then
        log "内容不是有效 ipk（可能是 404/错误页），跳过此地址"
        rm -f "$_out"
      else
        return 0
      fi
    fi
    log "该地址失败，尝试下一个..."
  done
  return 1
}

# 按候选架构依次尝试（精确架构优先，其次同家族回退）
USED_ARCH=""; IPK=""; TMP=""; tried=""
for cand in $(arch_candidates "$ARCH"); do
  case " $tried " in *" $cand "*) continue ;; esac
  tried="$tried $cand"
  f="larktun_${PKG_VERSION}_owrt${PUB}_${cand}.ipk"
  log "尝试架构包: $f"
  if download_mirrors "$f" "/tmp/$f" gzip; then
    USED_ARCH="$cand"; IPK="$f"; TMP="/tmp/$f"; break
  fi
done
[ -n "$USED_ARCH" ] || die "未找到适配 系列 $PUB / 架构 $ARCH 的包（已尝试兼容架构: $(arch_candidates "$ARCH")）。"

FORCE_ARCH=""
if [ "$USED_ARCH" != "$ARCH" ]; then
  # 不同 opkg 版本接受非本机架构包的参数不同：新版用 --add-arch，旧版用 --force-architecture
  OPKG_HELP=$(opkg 2>&1 || true)
  if echo "$OPKG_HELP" | grep -q -- '--add-arch'; then
    FORCE_ARCH="--add-arch ${USED_ARCH}:1"
  elif echo "$OPKG_HELP" | grep -q -- '--force-architecture'; then
    FORCE_ARCH="--force-architecture"
  else
    log "警告: 此 opkg 不支持架构覆盖参数，安装非本机架构包可能失败"
  fi
  log "无精确架构包，改用兼容包 $USED_ARCH（静态 arm64 可在 $ARCH 运行），参数: ${FORCE_ARCH:-(无)}"
fi

# 4. sha256 校验（若下载站提供 .sha256）
SHA_TMP="${TMP}.sha256"
if download_mirrors "${IPK}.sha256" "$SHA_TMP" 2>/dev/null && [ -s "$SHA_TMP" ]; then
  EXP=$(awk '{print $1}' "$SHA_TMP" | tr -d '[:space:]')
  GOT=$(sha256sum "$TMP" | awk '{print $1}')
  [ "$EXP" = "$GOT" ] || die "sha256 校验失败：期望 $EXP，实际 $GOT"
  log "sha256 校验通过"
  rm -f "$SHA_TMP"
else
  log "未提供 sha256，跳过校验"
fi

# 5. 安装（自动拉取依赖 ca-bundle / kmod-tun）
FORCE_DEPENDS=""
[ -n "${LARKTUN_FORCE_DEPENDS:-}" ] && FORCE_DEPENDS="--force-depends" || true

log "更新软件列表..."
opkg update || log "opkg update 失败（软件源不可用？），继续尝试安装"
opkg remove larktun 2>/dev/null || true

OPKG_LOG=/tmp/larktun-opkg.log
ok=0; i=1
while [ $i -le 3 ]; do
  # shellcheck disable=SC2086
  if opkg install $FORCE_ARCH $FORCE_DEPENDS "$TMP" >"$OPKG_LOG" 2>&1; then
    cat "$OPKG_LOG"; ok=1; break
  fi
  cat "$OPKG_LOG"
  log "安装失败，重试 $i/3 ..."; i=$((i + 1)); sleep 2
done

if [ "$ok" != "1" ]; then
  if grep -qiE 'kmod-tun|satisfy|unsatisfied|cannot find dependency' "$OPKG_LOG" 2>/dev/null; then
    err "依赖未能安装。常见原因：本固件为 SNAPSHOT/自编译，kmod-tun 与内核精确版本不匹配，或软件源不可用。"
    err "若设备已具备 TUN（容器/虚拟化环境通常 /dev/net/tun 已存在），可忽略依赖强制安装，重跑："
    err "    LARKTUN_FORCE_DEPENDS=1 sh \$0"
  fi
  die "opkg 安装失败，请查看上方 opkg 实际输出定位原因。"
fi
rm -f "$TMP" "$OPKG_LOG"

# 6. 旧版本(fw3/iptables) 自动切换防火墙后端
if [ ! -x /sbin/fw4 ] && ! command -v nft >/dev/null 2>&1; then
  log "检测到 iptables(fw3) 环境，设置 fw_mode=iptables"
  { uci set larktun.settings.fw_mode='iptables' && uci commit larktun; } 2>/dev/null \
    || sed -i "s/option fw_mode .*/option fw_mode 'iptables'/" /etc/config/larktun 2>/dev/null \
    || true
fi

# 7. 启动服务
log "启用并启动 larktun 服务..."
/etc/init.d/larktun enable
/etc/init.d/larktun restart
sleep 2
command -v larktun >/dev/null 2>&1 || die "安装后未找到 larktun 命令，请检查安装日志。"

# 8. 入网（22.03 需 --netfilter-mode=off）
EXTRA=""
[ "$SERIES" = "22.03" ] && EXTRA="--netfilter-mode=off" || true

if [ -n "$AUTHKEY" ]; then
  log "使用 authkey 全自动登录入网..."
  # shellcheck disable=SC2086
  larktun up --authkey="$AUTHKEY" $HOSTNAME_ARG $EXTRA $UP_ARGS
  log "完成！用 'larktun status' 查看状态。"
else
  echo ""
  echo "┌─ Larktun 安装完成！"
  echo "│ 该设备需登录后才能入网，执行："
  echo "│"
  echo "│   larktun up $HOSTNAME_ARG $EXTRA $UP_ARGS"
  echo "│"
  echo "│ 想完全免人工：在 Tailscale 后台生成 auth key 后重跑本脚本："
  echo "│   LARKTUN_AUTHKEY=tskey-xxxx sh \$0"
  echo "└─"
fi
