#!/usr/bin/env bash
# subctl —— 直连出口 VPS 的免密登录、订阅服务开关、服务日志与节点二维码（薄封装，复用 connect_to.sh 生成的专用密钥）。
#
# 前置:
#   - 已用 setup_direct.sh 部署过（它会配好免密并记住这台 VPS）；本脚本只用密钥、不碰密码。
#   - 目标来源：--host 参数 > ${XDG_CONFIG_HOME:-~/.config}/ownexit/direct/ 下唯一的目标配置（见 target_lib.sh）。
#   - qr 读本机 setup_direct.sh 渲染的订阅（${XDG_STATE_HOME:-~/.local/state}/ownexit/direct/<目标>/），需要 qrencode。
#   - 不应被 source。

set -euo pipefail

HOST=""
SSH_USER="root"
SSH_PORT="22"
SUB_SERVICE="ownexit-subscription"

usage() {
  cat <<'EOF'
subctl —— 直连出口 VPS 的免密登录、订阅服务开关、日志与二维码

用法:
  subctl [--host <ip>] [--port <port>] [--user <user>] [子命令]

子命令:
  login        免密登录 VPS（交互式 SSH，默认）
  start        启动订阅服务（给新设备导入订阅时临时打开；用完记得 stop）
  stop         停止订阅服务（平时应保持关闭，缩小公网暴露）
  status       查看代理服务与订阅服务状态
  log [行数]   查看代理服务最近的日志，默认 100 行
  qr           在终端显示节点二维码（需要 qrencode；读本机订阅，不连 VPS）
  devices      列出 VPS 上的设备与本机记录的各设备订阅地址（只读）
  help         显示本帮助（同 -h / --help）

示例:
  subctl status                         # 使用上次 setup_direct.sh 记住的 VPS
  subctl start
  subctl log 300
  subctl qr
  subctl devices
  subctl --host 203.0.113.7 stop        # 记住了多台 VPS 时指定其中一台

退出码: 0 成功；1 远端操作失败或缺少密钥；2 参数错误或无法确定目标。
EOF
}

die() { echo "[!] $*" >&2; exit 1; }

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=target_lib.sh
. "${SCRIPT_DIR}/target_lib.sh"

CMD=""
LOG_LINES=100
while [[ $# -gt 0 ]]; do
  case "$1" in
    --host)    HOST="${2:?--host 需要一个参数}"; shift 2 ;;
    --host=*)  HOST="${1#*=}"; shift ;;
    --port)    SSH_PORT="${2:?--port 需要一个参数}"; shift 2 ;;
    --port=*)  SSH_PORT="${1#*=}"; shift ;;
    --user)    SSH_USER="${2:?--user 需要一个参数}"; shift 2 ;;
    --user=*)  SSH_USER="${1#*=}"; shift ;;
    help|-h|--help) usage; exit 0 ;;
    login|start|stop|status|qr|devices)
      [[ -z "${CMD}" ]] || die_usage "只能给一个子命令（已有 ${CMD}）"
      CMD="$1"; shift ;;
    log)
      [[ -z "${CMD}" ]] || die_usage "只能给一个子命令（已有 ${CMD}）"
      CMD=log; shift
      if [[ $# -gt 0 && "$1" =~ ^[0-9]+$ ]]; then LOG_LINES="$1"; shift; fi ;;
    *) die_usage "未知参数或子命令: $1（可用: login / start / stop / status / log / qr / devices / help）" ;;
  esac
done
CMD="${CMD:-login}"

# subctl 不负责首次部署：没有记住的目标时不交互提问，直接提示先跑 setup_direct.sh。
# shellcheck disable=SC2034  # 由 target_lib.sh 的 resolve_target 读取
TARGET_NO_PROMPT_HINT="还没有记住任何 VPS，请先运行 setup_direct.sh --host <ip> 完成部署，或给 subctl 加 --host"
resolve_target

# 密钥路径推导必须与 connect_to.sh / setup_direct.sh 完全一致
SAFE_NAME="$(target_safe_name "${SSH_USER}" "${HOST}" "${SSH_PORT}")"
KEY="${HOME}/.ssh/ownexit/id_ed25519_${SAFE_NAME}"

# qr 只读本机订阅文件，不需要连 VPS：放在密钥检查之前处理。
if [[ "${CMD}" == qr ]]; then
  command -v qrencode >/dev/null 2>&1 || die "没有 qrencode：macOS 运行 brew install qrencode，Debian/Ubuntu 运行 sudo apt install qrencode"
  STATE_DIR="${XDG_STATE_HOME:-${HOME}/.local/state}/ownexit/direct/${SAFE_NAME}"
  TOKEN="$(awk -F= '$1 == "TOKEN" {print $2; exit}' "${STATE_DIR}/state.env" 2>/dev/null || true)"
  NODE_FILE="${STATE_DIR}/ownexit-subscription/${TOKEN}/node.txt"
  [[ -n "${TOKEN}" && -f "${NODE_FILE}" ]] || die "本机没有这台 VPS 的订阅，先运行 ownexit direct --host ${HOST}"
  qrencode -t ANSIUTF8 < "${NODE_FILE}"
  exit 0
fi
[[ -f "${KEY}" ]] || die "未找到密钥 ${KEY}，请先运行 setup_direct.sh --host ${HOST} 完成部署"

# SetEnv 强制给远端发 VPS 上存在的 locale，覆盖本机转发来的 zh_CN.UTF-8，
# 避免远端 bash setlocale 失败刷 "cannot change locale" 警告（VPS 未生成 zh_CN）
SSH_BASE=(-i "${KEY}" -p "${SSH_PORT}" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=12 -o SetEnv=LC_ALL=C.UTF-8)
TGT="${SSH_USER}@${HOST}"

# 非交互远程命令额外加 BatchMode，避免密钥失效时挂在密码提示上
rcmd() { ssh "${SSH_BASE[@]}" -o BatchMode=yes "${TGT}" "$@"; }

case "${CMD}" in
  login)
    exec ssh "${SSH_BASE[@]}" "${TGT}"
    ;;
  start)
    rcmd "systemctl start '${SUB_SERVICE}'"
    echo "[+] 已启动 ${SUB_SERVICE}（临时开；客户端更新完订阅后执行 subctl stop 关回）"
    ;;
  stop)
    rcmd "systemctl stop '${SUB_SERVICE}'"
    echo "[+] 已停止 ${SUB_SERVICE}"
    ;;
  status)
    rcmd "bash -s" <<REMOTE
if [ -e /etc/systemd/system/ownexit-direct.service ]; then
  echo -n '代理服务 ownexit-direct : '; systemctl is-active ownexit-direct 2>/dev/null || true
elif [ -L /usr/local/bin/sb ]; then
  echo -n '代理服务（旧版 233boy）  : '; systemctl is-active sing-box 2>/dev/null || true
  echo '  建议运行 ownexit direct --migrate 换成本项目的服务（参数不变，客户端不用动）'
else
  echo '代理服务               : 未安装'
fi
echo -n '订阅服务（运行）        : '; systemctl is-active ${SUB_SERVICE} 2>/dev/null || true
echo -n '订阅服务（开机自启）    : '; systemctl is-enabled ${SUB_SERVICE} 2>/dev/null || true
REMOTE
    ;;
  log)
    # 旧版 233boy 的服务名是 sing-box；迁移后是 ownexit-direct。
    rcmd "if [ -e /etc/systemd/system/ownexit-direct.service ]; then u=ownexit-direct; else u=sing-box; fi; journalctl -u \"\$u\" -n ${LOG_LINES} --no-pager"
    ;;
  devices)
    # 只读：服务器的设备表是权威源（只取名字，不取 UUID）；订阅地址来自本机记录的 TOKEN。
    names="$(rcmd "awk -F= 'NF {print \$1}' /etc/ownexit-direct/devices.env 2>/dev/null || true")" || die "无法读取 VPS 上的设备表"
    STATE_DIR="${XDG_STATE_HOME:-${HOME}/.local/state}/ownexit/direct/${SAFE_NAME}"
    SUB_PORT="$(awk -F= '$1 == "SUB_PORT" {print $2; exit}' "${STATE_DIR}/state.env" 2>/dev/null || true)"
    TOKEN="$(awk -F= '$1 == "TOKEN" {print $2; exit}' "${STATE_DIR}/state.env" 2>/dev/null || true)"
    echo "default : http://${HOST}:${SUB_PORT:-?}/${TOKEN:-?}/clash.yaml（及同目录 shadowrocket.txt / sing-box.json / node.txt）"
    while IFS= read -r name; do
      [[ -n "${name}" ]] || continue
      tok="$(awk -F= -v n="${name}" '$1 == n {print $2; exit}' "${STATE_DIR}/devices.env" 2>/dev/null || true)"
      if [[ -n "${tok}" ]]; then
        echo "${name} : http://${HOST}:${SUB_PORT:-?}/${tok}/clash.yaml（及同目录其它格式）"
      else
        echo "${name} : 本机没有它的订阅（换过电脑？），重跑 ownexit direct --host ${HOST} 生成"
      fi
    done <<< "${names}"
    ;;
esac
