#!/usr/bin/env bash
# Public prebuilt installer. Run from a downloaded file, not a partial pipe.
set -euo pipefail
umask 077

DOWNLOAD_URL=https://doc.linkerhub.work/linker-dashboard
CLOUD_URL=https://doc.linkerhub.work/linker-dashboard/cloud
SERVICE=linker-dashboard.service
VERSION=
CHECK_ONLY=0
fail() { echo "错误：$*" >&2; exit 1; }
usage() {
  echo "用法：bash install-linker-dashboard.sh [--check] [--version v26.9.3]"
  echo "默认安装公司下载站的稳定版；--check 仅下载校验，不安装、不需要 sudo。"
}
while (( $# )); do
  case "$1" in
    --check) CHECK_ONLY=1; shift ;;
    --version) (( $# >= 2 )) || fail "--version 缺少版本号"; VERSION=$2; shift 2 ;;
    -h|--help) usage; exit 0 ;;
    *) usage >&2; fail "未知参数：$1" ;;
  esac
done
[[ -z "$VERSION" || "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail "版本号格式应为 v26.9.3"
[[ $(uname -s) == Linux ]] || fail "此脚本仅支持 Linux"
[[ $(getconf LONG_BIT) == 64 ]] || fail "需要 64 位系统；32 位树莓派系统不能使用 ARM64 安装包"
case "$(uname -m)" in
  aarch64|arm64) ARCH=arm64; LIBRARY=libcanbus_arm64.so ;;
  x86_64|amd64) ARCH=amd64; LIBRARY=libcanbus.so ;;
  *) fail "没有适合此架构的预编译安装包：$(uname -m)" ;;
esac
if (( ! CHECK_ONLY )); then
  [[ -f /etc/debian_version ]] || fail "自动安装支持 64 位 Raspberry Pi OS、Debian 和 Ubuntu；其他系统请手动安装发布包"
  [[ -d /run/systemd/system ]] || fail "需要正在运行的 systemd，请在树莓派系统终端执行"
  if (( EUID != 0 )); then
    command -v sudo >/dev/null || fail "安装需要 root 或本机 sudo 权限"
    args=()
    [[ -z "$VERSION" ]] || args=(--version "$VERSION")
    exec sudo -- bash "$(readlink -f -- "${BASH_SOURCE[0]}")" "${args[@]}"
  fi
fi
for cmd in curl tar sha256sum awk mktemp; do
  command -v "$cmd" >/dev/null || fail "缺少 $cmd；Debian/Ubuntu 可运行 sudo apt-get install curl ca-certificates tar coreutils gawk"
done

tmp_dir=$(mktemp -d)
backup_dir=
stopped=0
changed=0
was_active=0
was_enabled=0
managed=(usr/local/bin/linker-dashboard usr/local/bin/libcanbus.so usr/local/bin/libcanbus_arm64.so
  usr/local/bin/HCanbus.dll etc/linker-dashboard.env etc/systemd/system/linker-dashboard.service)
rollback() {
  echo "安装未完成，正在恢复原服务……" >&2
  if (( changed )); then
    if [[ -f /etc/systemd/system/linker-dashboard.service ]]; then
      systemctl daemon-reload || return 1
      systemctl stop "$SERVICE" || return 1
      systemctl disable "$SERVICE" || return 1
    fi
    for path in "${managed[@]}"; do
      rm -f -- "/$path" || return 1
    done
    # Retain the failed run's data for diagnosis; restore the stopped snapshot.
    if [[ -e /var/lib/linker-dashboard ]]; then
      mv /var/lib/linker-dashboard "$backup_dir/failed-data" || return 1
    fi
    cp -a "$backup_dir/root/." / || return 1
    systemctl daemon-reload || return 1
    if (( was_enabled )); then
      systemctl enable "$SERVICE" || return 1
    fi
  fi
  if (( was_active )); then
    systemctl start "$SERVICE" || return 1
    systemctl is-active --quiet "$SERVICE" || return 1
  fi
}
cleanup() {
  local result=$?
  trap - EXIT INT TERM
  if (( result != 0 && stopped )); then
    if ! rollback; then
      echo "自动恢复未完成，请保留备份并检查 systemctl status $SERVICE。" >&2
    fi
  fi
  [[ -z "$backup_dir" ]] || echo "安装前备份：$backup_dir（仅 root 可读）"
  rm -rf -- "$tmp_dir"
  exit "$result"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
download() {
  curl --fail --show-error --location --proto '=https' --proto-redir '=https' \
    --connect-timeout 15 --max-time 600 --retry 2 --output "$2" "$1"
}
if [[ -z "$VERSION" ]]; then
  download "$DOWNLOAD_URL/latest-version.txt" "$tmp_dir/version"
  VERSION=$(cat "$tmp_dir/version")
fi
[[ "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail "下载站返回了无效版本号"
package="linker-dashboard-$VERSION-linux-$ARCH"
archive="$package.tar.gz"
echo "下载并校验 $VERSION / Linux $ARCH……"
download "$DOWNLOAD_URL/download/$VERSION/SHA256SUMS" "$tmp_dir/SHA256SUMS"
expected=$(awk -v name="$archive" '$2 == name || $2 == "*" name || $2 == "./" name {print $1}' "$tmp_dir/SHA256SUMS")
[[ "$expected" =~ ^[a-fA-F0-9]{64}$ ]] || fail "校验清单缺少此安装包，或包含重复/无效记录"
download "$DOWNLOAD_URL/download/$VERSION/$archive" "$tmp_dir/$archive"
printf '%s  %s\n' "$expected" "$tmp_dir/$archive" | sha256sum --check --status || fail "安装包 SHA-256 校验失败，未安装"
mkdir "$tmp_dir/unpacked"
tar -xzf "$tmp_dir/$archive" -C "$tmp_dir/unpacked" --no-same-owner --no-same-permissions \
  "$package/linker-dashboard" "$package/$LIBRARY" "$package/install-service.sh" "$package/linker-dashboard.service"
package_dir="$tmp_dir/unpacked/$package"
for name in linker-dashboard "$LIBRARY" install-service.sh linker-dashboard.service; do
  [[ -f "$package_dir/$name" && ! -L "$package_dir/$name" ]] || fail "安装包缺少有效文件：$name"
done
bash -n "$package_dir/install-service.sh"
if (( CHECK_ONLY )); then
  echo "校验通过：$VERSION / Linux $ARCH；未安装，未启动服务。"
  exit 0
fi

# Serialise installations. Downloading/verification above never stops a service.
command -v flock >/dev/null || fail "缺少 flock，请安装 util-linux"
exec 9>/run/linker-dashboard-install.lock
flock -n 9 || fail "另一项安装正在进行，请稍后重试"
state=$(systemctl is-enabled "$SERVICE" 2>/dev/null || true)
[[ "$state" != masked* ]] || fail "服务已被屏蔽，请先确认是否允许重新启用"
[[ "$state" != enabled ]] || was_enabled=1
systemctl is-active --quiet "$SERVICE" && was_active=1
dropins=$(systemctl show "$SERVICE" --property=DropInPaths --value)
fragment=$(systemctl show "$SERVICE" --property=FragmentPath --value)
[[ -z "$dropins" ]] || fail "检测到自定义 systemd 配置，请使用手动升级以保留自定义参数"
[[ -z "$fragment" || "$fragment" == /etc/systemd/system/linker-dashboard.service ]] || fail "服务位于自定义路径，请使用手动升级"
if [[ -f /etc/systemd/system/linker-dashboard.service ]]; then
  cmp -s /etc/systemd/system/linker-dashboard.service "$package_dir/linker-dashboard.service" || fail "服务文件有自定义设置或与此版本不同，请使用手动升级"
fi
for path in "${managed[@]}" var/lib/linker-dashboard; do
  [[ ! -L /$path ]] || fail "安装目标 /$path 是符号链接，请使用手动安装"
done
for path in "${managed[@]}"; do
  [[ ! -e /$path || -f /$path ]] || fail "安装目标 /$path 不是普通文件，请检查后手动安装"
done
[[ ! -e /var/lib/linker-dashboard || -d /var/lib/linker-dashboard ]] || fail "运行数据路径不是目录"
if mountpoint -q /var/lib/linker-dashboard; then
  fail "运行数据目录是独立挂载点，请使用手动升级及备份"
fi
# Flag help exits before hardware discovery; catches an incompatible executable.
chmod 0755 "$package_dir/linker-dashboard"
"$package_dir/linker-dashboard" -h >/dev/null 2>&1 || fail "程序无法在此系统运行，尚未替换现有服务"
if ! command -v ip >/dev/null || ! command -v udevadm >/dev/null; then
  echo "安装运行依赖 iproute2、udev……"
  apt-get update
  DEBIAN_FRONTEND=noninteractive apt-get install -y iproute2 udev
fi
if (( ! was_active )) && curl --silent --max-time 2 --noproxy '*' http://127.0.0.1:7081/ >/dev/null; then
  fail "7081 端口已有其他程序，请先停止该程序再安装"
fi

install -d -m 0700 /var/backups/linker-dashboard
backup_dir=$(mktemp -d /var/backups/linker-dashboard/install-XXXXXXXX)
mkdir "$backup_dir/root"
printf 'version=%s\nwas_active=%s\nwas_enabled=%s\n' "$VERSION" "$was_active" "$was_enabled" > "$backup_dir/state"
echo "停止服务并备份程序、配置和运行数据……"
stopped=1
if [[ -n "$fragment" ]]; then
  systemctl stop "$SERVICE"
fi
for path in "${managed[@]}" var/lib/linker-dashboard; do
  if [[ -e /$path ]]; then
    cp -a --parents "/$path" "$backup_dir/root/"
  fi
done
changed=1
if [[ ! -f /etc/linker-dashboard.env ]]; then
  # Published older packages contain a legacy address. Never replace an existing
  # environment file or cloud.json, since either may hold an enrolled identity.
  if [[ -f /var/lib/linker-dashboard/cloud.json ]]; then
    printf '# Keep the cloud address saved in cloud.json.\n' > "$tmp_dir/first-install.env"
  else
    printf 'LINKER_CLOUD_URL=%s\n' "$CLOUD_URL" > "$tmp_dir/first-install.env"
  fi
  bash "$package_dir/install-service.sh" "$package_dir/linker-dashboard" "$tmp_dir/first-install.env"
else
  bash "$package_dir/install-service.sh" "$package_dir/linker-dashboard"
fi
echo "检查服务与本机 API……"
healthy=0
for (( attempt=0; attempt<30; attempt++ )); do
  if systemctl is-active --quiet "$SERVICE" && \
    curl --fail --silent --max-time 2 --noproxy '*' http://127.0.0.1:7081/api/controller/info > "$tmp_dir/info" && \
    awk -F '"' -v version="$VERSION" '{for (i=2; i<NF-1; i++) if ($i=="version" && $(i+1) ~ /^[[:space:]]*:[[:space:]]*$/ && $(i+2)==version) ok=1} END {exit !ok}' "$tmp_dir/info"; then
    healthy=1
    break
  fi
  sleep 1
done
(( healthy )) || fail "服务未在预期端口返回 $VERSION；请用 journalctl -u $SERVICE 查看日志"
stopped=0
echo "已安装 $VERSION，并启用开机自启动。"
echo "本机访问：http://127.0.0.1:7081；其他设备访问：http://<树莓派IP>:7081"
echo "数据：/var/lib/linker-dashboard；云配置：/etc/linker-dashboard.env"
echo "部署不需要云账号；云端使用需管理员邀请注册后生成设备接入码。"
