#!/bin/bash
# =============================================================================
# agent 模板虚拟机脚本（Ubuntu 24.04）
#
#   bash agent-template.sh install          # ① 在模板源虚拟机里：装好所有东西（可重复执行）
#   bash agent-template.sh cleanup          # ② 转换成模板之前：清理身份信息和登录凭证，然后关机
#   bash agent-template.sh init agent-02    # ③ 每台克隆出来的新机器：改名、登录各个 agent、启动 T3 后台服务
#   bash agent-template.sh check            # 随时检查：时区、时间、IP、代理、工具、机器身份
#
# 装的 agent：Claude Code、Codex CLI、OpenCode（都只安装不登录，登录在 init 时每台单独做）
#
# 用普通用户（如 dev）执行，不要用 root 或 sudo bash；需要权限的地方脚本会自己调用 sudo。
# 约定：代理 = proxy 容器 192.168.50.101:7890，官方软件源（不用国内镜像），纽约时区。
# =============================================================================
set -euo pipefail

PROXY_HOST=192.168.50.101
PROXY_PORT=7890
P="http://${PROXY_HOST}:${PROXY_PORT}"
NP="localhost,127.0.0.1,::1,192.168.0.0/16,10.0.0.0/8,.lan"
TZ_NAME="America/New_York"

say()  { printf '\n\033[1;34m== %s ==\033[0m\n' "$*"; }
ok()   { printf '  \033[32m✓\033[0m %s\n' "$*"; }
warn() { printf '  \033[33m!\033[0m %s\n' "$*"; }
die()  { printf '\033[31m✗ %s\033[0m\n' "$*" >&2; exit 1; }
ask()  { local a; read -r -p "$1 [y/N] " a; [[ "$a" =~ ^[Yy]$ ]]; }

[ "$(id -u)" != "0" ] || die "请用普通用户（比如 dev）执行，不要用 root 或 sudo bash。"
export PATH="$HOME/.local/bin:$PATH"

# -----------------------------------------------------------------------------
# 全局代理 + 时区（和 agent-global-proxy.sh 相同，可重复执行）
# -----------------------------------------------------------------------------
setup_proxy() {
  say "时区：$TZ_NAME"
  sudo timedatectl set-timezone "$TZ_NAME"

  say "全局代理：$P"
  # 登录会话
  sudo sed -i '/_proxy=/Id;/^NODE_USE_ENV_PROXY=/d;/^COREPACK_ENABLE_DOWNLOAD_PROMPT=/d' /etc/environment
  sudo tee -a /etc/environment >/dev/null <<EOT
http_proxy=$P
https_proxy=$P
all_proxy=$P
HTTP_PROXY=$P
HTTPS_PROXY=$P
ALL_PROXY=$P
no_proxy=$NP
NO_PROXY=$NP
NODE_USE_ENV_PROXY=1
COREPACK_ENABLE_DOWNLOAD_PROMPT=0
EOT
  # apt
  sudo tee /etc/apt/apt.conf.d/95proxy >/dev/null <<EOT
Acquire::http::Proxy "$P";
Acquire::https::Proxy "$P";
EOT
  # sudo 保留代理变量（先检查语法再写入）
  local tmp; tmp=$(mktemp)
  echo 'Defaults env_keep += "http_proxy https_proxy all_proxy no_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY NODE_USE_ENV_PROXY COREPACK_ENABLE_DOWNLOAD_PROMPT"' > "$tmp"
  if sudo visudo -cf "$tmp" >/dev/null; then
    sudo install -m 440 -o root -g root "$tmp" /etc/sudoers.d/proxy
  else
    warn "sudoers 规则检查失败，跳过"
  fi
  rm -f "$tmp"
  # systemd 系统服务（Docker 等）和用户服务（T3 Code 后台服务等）
  local d
  for d in system user; do
    sudo mkdir -p /etc/systemd/$d.conf.d
    sudo tee /etc/systemd/$d.conf.d/proxy.conf >/dev/null <<EOT
[Manager]
DefaultEnvironment="http_proxy=$P" "https_proxy=$P" "HTTP_PROXY=$P" "HTTPS_PROXY=$P" "no_proxy=$NP" "NO_PROXY=$NP" "NODE_USE_ENV_PROXY=1"
EOT
  done
  sudo systemctl daemon-reexec
  # 用户管理器开了 linger 后会一直运行，重新登录也不会重读配置，要让它重新加载
  systemctl --user daemon-reexec 2>/dev/null || true
  # 当前脚本后续步骤也走代理
  export http_proxy=$P https_proxy=$P HTTP_PROXY=$P HTTPS_PROXY=$P no_proxy=$NP NO_PROXY=$NP
  ok "登录会话、sudo、apt、systemd 服务都已配置代理"
}

# -----------------------------------------------------------------------------
# ① install：模板源虚拟机里装好所有东西
# -----------------------------------------------------------------------------
cmd_install() {
  sudo -v
  setup_proxy

  say "基础软件包"
  sudo apt-get update
  sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
    git build-essential tmux htop unzip jq gh curl ca-certificates gnupg \
    libatomic1 netcat-openbsd mosh qemu-guest-agent
  sudo systemctl start qemu-guest-agent || true
  ok "git、编译工具、tmux、gh、mosh、qemu-guest-agent 等"

  say "SSH 方式访问 GitHub 走代理"
  mkdir -p ~/.ssh && chmod 700 ~/.ssh
  touch ~/.ssh/config && chmod 600 ~/.ssh/config
  grep -q 'ProxyCommand nc' ~/.ssh/config || cat >> ~/.ssh/config <<EOT
Host github.com
  ProxyCommand nc -X connect -x ${PROXY_HOST}:${PROXY_PORT} %h %p
EOT
  ok "~/.ssh/config"

  if command -v snap >/dev/null; then
    sudo snap set system proxy.http="$P" proxy.https="$P" && ok "snap 代理"
  fi

  say "Node.js 24 + pnpm"
  if ! node -v 2>/dev/null | grep -q '^v24\.'; then
    curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
    sudo apt-get install -y nodejs
  fi
  sudo corepack enable
  ok "node $(node -v)，pnpm $(COREPACK_ENABLE_DOWNLOAD_PROMPT=0 pnpm -v 2>/dev/null || echo '首次使用时下载')"

  say "Docker"
  if ! command -v docker >/dev/null; then
    curl -fsSL https://get.docker.com | sudo -E sh
  fi
  sudo usermod -aG docker "$USER"
  # 容器内部运行、docker build 时也走代理
  mkdir -p ~/.docker
  [ -s ~/.docker/config.json ] || echo '{}' > ~/.docker/config.json
  jq --arg p "$P" --arg np "$NP" \
    '.proxies.default = {httpProxy:$p, httpsProxy:$p, noProxy:$np}' \
    ~/.docker/config.json > ~/.docker/config.json.tmp && mv ~/.docker/config.json.tmp ~/.docker/config.json
  sudo systemctl restart docker
  ok "Docker $(docker --version 2>/dev/null | awk '{print $3}' | tr -d ,)（daemon 和容器都走代理）"

  say "Claude Code（只安装，不登录）"
  command -v claude >/dev/null || curl -fsSL https://claude.ai/install.sh | bash
  ok "claude $(claude --version 2>/dev/null | head -1)"

  say "Codex CLI（只安装，不登录）"
  npm config set prefix "$HOME/.local"          # npm 全局包装到 ~/.local，不需要 sudo
  command -v codex >/dev/null || npm install -g @openai/codex
  ok "codex $(codex --version 2>/dev/null | head -1)"

  say "OpenCode（只安装，不登录）"
  command -v opencode >/dev/null || curl -fsSL https://opencode.ai/install | XDG_BIN_DIR="$HOME/.local/bin" bash
  ok "opencode $(opencode --version 2>/dev/null | head -1)"

  say "T3 Code 命令行（只安装，不启动服务、不连接）"
  command -v t3 >/dev/null || curl -fsSL https://t3.codes/install.sh | sh
  ok "$(t3 --version 2>/dev/null | head -1)"

  say "PATH、tmux、linger"
  grep -q 'HOME/.local/bin' ~/.bashrc || echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
  [ -f ~/.tmux.conf ] || cat > ~/.tmux.conf <<'EOT'
set -g mouse on
set -g history-limit 50000
EOT
  sudo loginctl enable-linger "$USER"
  mkdir -p ~/NocoBase
  ok "~/.local/bin 已加入 PATH；~/.tmux.conf；后台服务允许在退出登录后运行"

  say "完成"
  cat <<EOT
  下一步：
  1. 退出 SSH 重新登录（让 docker 用户组和代理变量生效），然后检查：
       bash ~/agent-template.sh check
  2.（可选）搬 Claude 通用配置、在 Mac 上配好 SSH 免密、预装公开仓库源码
  3. 拍快照 → 从快照克隆出 9000 tpl-agent → 在 9000 里执行：bash agent-template.sh cleanup
EOT
}

# -----------------------------------------------------------------------------
# ② cleanup：转换成模板之前执行（在要做成模板的那台机器里）
# -----------------------------------------------------------------------------
cmd_cleanup() {
  say "检查不该进入模板的登录凭证"
  local found=() f
  for f in ~/.claude/.credentials.json ~/.claude.json ~/.codex/auth.json ~/.local/share/opencode/auth.json \
           ~/.config/gh ~/.git-credentials ~/.t3/userdata; do
    [ -e "$f" ] && found+=("$f")
  done
  for f in ~/.ssh/id_*; do [ -e "$f" ] && found+=("$f"); done
  if [ ${#found[@]} -gt 0 ]; then
    printf '  %s\n' "${found[@]}"
    ask "以上文件含登录信息或机器身份，删除后继续？" || die "已取消"
  else
    ok "没有发现登录凭证"
  fi

  if t3 service status >/dev/null 2>&1; then
    t3 service uninstall --yes 2>/dev/null || t3 service uninstall || true
    ok "已卸载 T3 后台服务（克隆机各自安装）"
  fi
  if [ ${#found[@]} -gt 0 ]; then rm -rf "${found[@]}"; ok "已删除登录凭证"; fi

  say "让每台克隆机开机时生成自己的 SSH 主机密钥"
  sudo mkdir -p /etc/systemd/system/ssh.service.d
  sudo tee /etc/systemd/system/ssh.service.d/regen-keys.conf >/dev/null <<'EOT'
[Service]
ExecStartPre=
ExecStartPre=/usr/bin/ssh-keygen -A
ExecStartPre=/usr/sbin/sshd -t
EOT
  sudo rm -f /etc/ssh/ssh_host_*
  ok "已删除现有主机密钥"

  say "清空 machine-id（克隆机会自动生成新的，避免分到同一个 IP）"
  sudo truncate -s 0 /etc/machine-id
  sudo rm -f /var/lib/dbus/machine-id
  sudo ln -s /etc/machine-id /var/lib/dbus/machine-id
  ok "machine-id 已清空"

  say "清理缓存和历史"
  sudo apt-get clean
  sudo journalctl --vacuum-time=1s >/dev/null 2>&1 || true
  rm -f ~/.bash_history ~/.lesshst ~/.viminfo
  ok "apt 缓存、日志、命令历史"

  say "完成"
  echo "  接下来关机，然后在 PVE 里右键这台虚拟机 → 转换成模板。"
  echo "  注意：关机前不要再重启它，否则会重新生成 machine-id 和密钥。"
  if ask "现在关机？"; then unset HISTFILE; sudo poweroff; fi
}

# -----------------------------------------------------------------------------
# ③ init：每台克隆出来的新机器执行一次
# -----------------------------------------------------------------------------
cmd_init() {
  local new="${1:-}"
  [ -n "$new" ] || die "用法：bash agent-template.sh init <主机名>，例如 init agent-02"
  sudo -v

  say "主机名：$(hostname) → $new"
  local old; old=$(hostname)
  sudo hostnamectl set-hostname "$new"
  sudo sed -i "s/\b${old}\b/${new}/g" /etc/hosts
  grep -q "\b${new}\b" /etc/hosts || echo "127.0.1.1 ${new}" | sudo tee -a /etc/hosts >/dev/null
  ok "主机名已改"

  say "检查机器身份"
  if [ ! -s /etc/machine-id ]; then
    sudo systemd-machine-id-setup >/dev/null
  fi
  ls /etc/ssh/ssh_host_*_key >/dev/null 2>&1 || sudo ssh-keygen -A
  ok "machine-id：$(cut -c1-8 /etc/machine-id)…，SSH 主机密钥已就绪"

  say "Git 身份"
  if [ -z "$(git config --global user.name || true)" ]; then
    local gn ge
    read -r -p "  Git 用户名（直接回车跳过）: " gn
    read -r -p "  Git 邮箱（直接回车跳过）: " ge
    [ -n "$gn" ] && git config --global user.name "$gn"
    [ -n "$ge" ] && git config --global user.email "$ge"
  fi
  ok "user.name=$(git config --global user.name || echo 未设置)"

  say "登录 Claude Code"
  if ask "现在登录 Claude（会给出一个链接，在 Mac 浏览器里打开授权）？"; then
    claude auth login || warn "登录失败，稍后可以手动执行 claude auth login"
  fi

  say "登录 Codex（可选）"
  if ask "现在登录 Codex（OpenAI 账号，设备码方式：在 Mac 浏览器里打开链接、输入代码）？"; then
    codex login --device-auth || warn "稍后可以手动执行 codex login --device-auth"
  fi

  say "登录 OpenCode（可选）"
  if ask "现在登录 OpenCode（选择模型服务商并填写凭证）？"; then
    opencode auth login || warn "稍后可以手动执行 opencode auth login"
  fi

  say "登录 GitHub（可选）"
  if ask "现在登录 GitHub？推荐用细粒度 token：选 Paste an authentication token"; then
    gh auth login && gh auth setup-git || warn "稍后可以手动执行 gh auth login && gh auth setup-git"
  fi

  say "T3 Code 后台服务"
  if ask "安装 T3 Code 后台服务（开机自动运行）？"; then
    t3 service install
    # 后台服务默认 PATH 不含 ~/.local/bin（找不到 claude/codex/opencode）；监听 0.0.0.0 让局域网和 Tailscale 能访问
    mkdir -p ~/.config/systemd/user/t3code.service.d
    cat > ~/.config/systemd/user/t3code.service.d/agent.conf <<EOT
[Service]
Environment=T3CODE_HOST=0.0.0.0
Environment=PATH=$HOME/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
EOT
    systemctl --user daemon-reload
    t3 service restart || true
    sleep 3
    t3 service status || true
    ss -ltn | grep -q '0.0.0.0:3773\|\*:3773' && ok "浏览器访问：http://$(hostname -I | awk '{print $1}'):3773（先执行 t3 pair 配对）" \
      || warn "T3 没有监听局域网地址，用 ss -ltnp | grep 3773 检查"
    if ask "现在设置 T3 Connect（手机、外网也能连）？"; then
      t3 connect || warn "稍后可以手动执行 t3 connect"
    fi
  fi

  say "完成"
  echo "  IP：$(hostname -I | awk '{print $1}')"
  echo "  重新登录后可以执行 bash ~/agent-template.sh check 做一次全面检查"
  echo "  记得：路由器里按 MAC 绑定固定 IP；Mac 的 ~/.ssh/config 加上 $new；"
  echo "        如果 SSH 提示主机密钥变了，在 Mac 上执行 ssh-keygen -R <IP>"
}

# -----------------------------------------------------------------------------
# check：检查时区、时间、IP、代理、工具和机器身份（随时可以执行）
# -----------------------------------------------------------------------------
cmd_check() {
  set +e
  local pass=0 fail=0
  c_ok()  { printf '  \033[32m✓\033[0m %s\n' "$*"; pass=$((pass+1)); }
  c_bad() { printf '  \033[31m✗\033[0m %s\n' "$*"; fail=$((fail+1)); }
  c_inf() { printf '    %s\n' "$*"; }

  say "时区和时间"
  local tz; tz=$(timedatectl show -p Timezone --value)
  [ "$tz" = "$TZ_NAME" ] && c_ok "时区 $tz（$(date '+%Y-%m-%d %H:%M:%S %Z')）" || c_bad "时区是 $tz，应该是 $TZ_NAME"
  [ "$(timedatectl show -p NTPSynchronized --value)" = "yes" ] && c_ok "已和时间服务器同步（NTP）" || c_bad "NTP 未同步：sudo systemctl restart systemd-timesyncd"
  local hdr remote now diff
  hdr=$(curl -sI -m 10 https://www.google.com | tr -d '\r' | awk -F': ' 'tolower($1)=="date"{print $2}')
  if [ -n "$hdr" ]; then
    remote=$(date -d "$hdr" +%s 2>/dev/null); now=$(date +%s); diff=$(( now > remote ? now - remote : remote - now ))
    [ "$diff" -le 60 ] && c_ok "系统时间准确（和 Google 服务器相差 ${diff} 秒）" || c_bad "系统时间相差 ${diff} 秒"
  else
    c_bad "取不到网络时间（访问 Google 失败，先看下面的代理检查）"
  fi

  say "IP"
  local lan; lan=$(hostname -I | awk '{print $1}')
  [[ "$lan" == 192.168.50.* ]] && c_ok "局域网 IP $lan" || c_bad "局域网 IP 是 $lan，不在 192.168.50.x"
  local j ip country city direct sip
  j=$(curl -s -m 10 https://ipinfo.io/json)
  ip=$(jq -r '.ip // empty' <<<"$j" 2>/dev/null); country=$(jq -r '.country // empty' <<<"$j" 2>/dev/null); city=$(jq -r '.city // empty' <<<"$j" 2>/dev/null)
  if [ -n "$ip" ] && [ "$country" != "CN" ]; then c_ok "出口 IP $ip（$city, $country）"; else c_bad "出口 IP ${ip:-取不到}（${country:-?}）：没有走代理"; fi
  sip=$(sudo -n curl -s -m 10 https://ipinfo.io/ip 2>/dev/null || sudo curl -s -m 10 https://ipinfo.io/ip)
  [ -n "$sip" ] && [ "$sip" = "$ip" ] && c_ok "sudo 下出口 IP 一致" || c_bad "sudo 下出口 IP 是 ${sip:-取不到}"
  direct=$(curl --noproxy '*' -s -m 10 https://ipinfo.io/ip)
  c_inf "不走代理时（家里宽带）的 IP：${direct:-取不到}"

  say "代理"
  [ "${https_proxy:-}" = "$P" ] && c_ok "当前会话代理变量" || c_bad "当前会话没有代理变量（退出 SSH 重新登录）"
  apt-config dump | grep -qi "$PROXY_HOST" && c_ok "apt 走代理" || c_bad "apt 没配代理"
  systemctl show-environment 2>/dev/null | grep -q "$PROXY_HOST" && c_ok "systemd 系统服务走代理" || c_bad "systemd 系统服务没有代理变量"
  systemctl --user show-environment 2>/dev/null | grep -q "$PROXY_HOST" && c_ok "systemd 用户服务走代理（T3 后台服务）" || c_bad "systemd 用户服务没有代理变量：systemctl --user daemon-reexec（装了 T3 服务的话再 t3 service restart）"
  local code; code=$(curl -s -o /dev/null -m 10 -w '%{http_code}' https://api.anthropic.com)
  [ "$code" != "000" ] && c_ok "能连上 Claude API（HTTP $code）" || c_bad "连不上 api.anthropic.com"
  code=$(curl -s -o /dev/null -m 10 -w '%{http_code}' https://api.openai.com)
  [ "$code" != "000" ] && c_ok "能连上 OpenAI API（HTTP $code）" || c_bad "连不上 api.openai.com"
  if grep -h '^URIs' /etc/apt/sources.list.d/ubuntu.sources 2>/dev/null | grep -qE 'cn\.archive|ustc|tuna|aliyun|huaweicloud'; then
    c_bad "apt 用的是国内镜像"; else c_ok "apt 用的是官方源"; fi

  say "工具（用登录 shell 检查，和 T3 通过 SSH 看到的一致）"
  local t v
  for t in node pnpm docker gh claude codex opencode t3; do
    v=$(bash -lc "command -v $t >/dev/null && $t --version 2>/dev/null | head -1")
    [ -n "$v" ] && c_ok "$t ${v}" || c_bad "$t 没装或不在 PATH 里"
  done
  if docker info >/dev/null 2>&1; then
    c_ok "不加 sudo 能用 docker"
    docker info 2>/dev/null | grep -qi "proxy.*$PROXY_HOST" && c_ok "Docker 后台服务走代理" || c_bad "Docker 后台服务没有代理：sudo systemctl restart docker"
  else
    c_bad "不加 sudo 用不了 docker（刚加入 docker 组的话，重新登录）"
  fi
  jq -e '.proxies.default.httpsProxy' ~/.docker/config.json >/dev/null 2>&1 && c_ok "Docker 容器内部走代理" || c_bad "~/.docker/config.json 没有容器代理"

  say "机器身份"
  c_inf "主机名：$(hostname)"
  [ -s /etc/machine-id ] && c_ok "machine-id：$(cut -c1-12 /etc/machine-id)…" || c_bad "machine-id 为空（重启一次会自动生成）"
  ls /etc/ssh/ssh_host_*_key >/dev/null 2>&1 && c_ok "SSH 主机密钥存在" || c_bad "没有 SSH 主机密钥：sudo ssh-keygen -A"
  systemctl is-active --quiet qemu-guest-agent && c_ok "QEMU Guest Agent 在运行" || c_bad "QEMU Guest Agent 没运行"
  [ "$(loginctl show-user "$USER" -p Linger --value 2>/dev/null)" = "yes" ] && c_ok "允许后台服务在退出登录后运行（linger）" || c_bad "linger 没开：sudo loginctl enable-linger $USER"
  if t3 service status >/dev/null 2>&1; then
    c_inf "T3 后台服务：已安装"
    if ss -ltn | grep -q '0.0.0.0:3773\|\*:3773'; then c_ok "T3 监听局域网：http://$(hostname -I | awk '{print $1}'):3773"
    else c_bad "T3 只监听本机（127.0.0.1），局域网访问不了：见 init 里的 agent.conf"; fi
    systemctl --user show t3code -p Environment 2>/dev/null | grep -q '.local/bin' && c_ok "T3 后台服务的 PATH 含 ~/.local/bin" || c_bad "T3 后台服务找不到 ~/.local/bin 里的 claude/codex/opencode"
  else
    c_inf "T3 后台服务：未安装（模板里本来就不装，克隆后 init 时安装）"
  fi

  echo
  if [ "$fail" -eq 0 ]; then printf '\033[32m全部通过（%d 项）\033[0m\n' "$pass"; else printf '\033[31m%d 项未通过\033[0m，%d 项通过\n' "$fail" "$pass"; fi
  [ "$fail" -eq 0 ]
}

case "${1:-}" in
  install) cmd_install ;;
  cleanup) cmd_cleanup ;;
  init)    shift; cmd_init "${1:-}" ;;
  check)   cmd_check ;;
  *) sed -n '2,14p' "$0"; exit 1 ;;
esac
