#!/usr/bin/env bash
# subctl —— ownexit direct 日常操作（login / sub start|stop / status / log / qr / devices）的实现：
# 直连出口 VPS 的免密登录、订阅服务开关、服务日志与节点二维码（薄封装，复用 connect_to.sh 生成的专用密钥）。
#
# 前置:
#   - 只由 setup_direct.sh 的 forward_to_subctl 带内部标记 OWNEXIT_VIA_DIRECT=1 调起；直接运行（原 ownexit subctl，
#     2.0 已移除）以退出码 2 结束并提示新写法。
#   - 已用 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() {
  if [[ "${OWNEXIT_UI_LANG}" == en ]]; then
    cat <<'EOF'
ownexit direct day-to-day operations -- key login, subscription service on/off, logs and QR code for the direct exit VPS

Usage:
  ownexit direct [--host <ip>] [--port <port>] [--user <user>] <operation>

Operations:
  login                     log in to the VPS with the key (interactive SSH)
  sub start [--ttl <duration>]
                            start the subscription service (briefly, to import on a new device); --ttl turns it off when due
                            (e.g. 30m, 2h, minutes when no unit is given, 1 minute to 24 hours); without it it stays on,
                            remember to stop it afterwards
  sub stop                  stop the subscription service (keep it off normally to reduce public exposure)
  status                    status of the proxy service and the subscription service
  log [lines]               recent proxy service log, 100 lines by default
  qr                        show the node QR code in the terminal (needs qrencode; reads the local subscription, no VPS connection)
  devices                   list the devices on the VPS and the subscription URLs recorded locally (read-only)

Examples:
  ownexit direct status                         # use the VPS remembered by the last ownexit direct up
  ownexit direct sub start --ttl 30m            # turn off after 30 minutes
  ownexit direct sub stop
  ownexit direct log 300
  ownexit direct qr
  ownexit direct devices
  ownexit direct --host 203.0.113.7 sub stop    # pick one when several VPSes are remembered

This file (direct/subctl) implements these operations for ownexit direct and cannot be run on its own; the former
ownexit subctl command was removed in 2.0.

Exit codes: 0 success; 1 remote operation failed or key missing; 2 argument error or target cannot be determined.
EOF
  else
    # i18n:zh-begin
    cat <<'EOF'
ownexit direct 的日常操作 —— 直连出口 VPS 的免密登录、订阅服务开关、日志与二维码

用法:
  ownexit direct [--host <ip>] [--port <port>] [--user <user>] <操作>

操作:
  login                     免密登录 VPS（交互式 SSH）
  sub start [--ttl <时长>]  启动订阅服务（给新设备导入订阅时临时打开）；--ttl 到时自动关闭（例：30m、2h，
                            不带单位按分钟，1 分钟到 24 小时），不给则一直开着，用完记得 sub stop
  sub stop                  停止订阅服务（平时应保持关闭，缩小公网暴露）
  status                    查看代理服务与订阅服务状态
  log [行数]                查看代理服务最近的日志，默认 100 行
  qr                        在终端显示节点二维码（需要 qrencode；读本机订阅，不连 VPS）
  devices                   列出 VPS 上的设备与本机记录的各设备订阅地址（只读）

示例:
  ownexit direct status                         # 使用上次 ownexit direct up 记住的 VPS
  ownexit direct sub start --ttl 30m            # 30 分钟后自动关闭
  ownexit direct sub stop
  ownexit direct log 300
  ownexit direct qr
  ownexit direct devices
  ownexit direct --host 203.0.113.7 sub stop    # 记住了多台 VPS 时指定其中一台

本文件（direct/subctl）是 ownexit direct 这些操作的实现，不能单独运行；原 ownexit subctl 命令已在 2.0 移除。

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

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

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 输出语言（中文 / 英文）的判断与 L 函数；target_lib.sh 的提示也依赖它，必须先 source。
# shellcheck source=i18n_lib.sh
. "${SCRIPT_DIR}/i18n_lib.sh"
# shellcheck source=target_lib.sh
. "${SCRIPT_DIR}/target_lib.sh"

# 原 ownexit subctl 已在 2.0 移除：没有内部标记 = 用户直接调用（ownexit subctl 或 bash direct/subctl），
# 在解析参数之前就退出 2，并按第一个操作词给出 ownexit direct 的新写法。必须先于解析：否则 --ttl 校验、
# 记住的目标解析等报错会盖掉这条提示。提示不带用户原来写的 --host 等参数（与 1.x 的废弃提示一致）。
if [[ "${OWNEXIT_VIA_DIRECT:-}" != 1 ]]; then
  NEW_FORM=login
  # skip_value：上一个词是要跟值的选项（--host 等），这个词是它的值，不能当操作词（如 --port log）。
  skip_value=0
  for arg in "$@"; do
    if (( skip_value )); then skip_value=0; continue; fi
    case "${arg}" in
      --host|--port|--user|--ttl)   skip_value=1 ;;
      start|stop)                   NEW_FORM="sub ${arg}"; break ;;
      login|status|log|qr|devices)  NEW_FORM="${arg}"; break ;;
      help|-h|--help)               NEW_FORM="--help"; break ;;
    esac
  done
  die_usage "$(L "ownexit subctl 已在 2.0 移除，改用：ownexit direct ${NEW_FORM}" "ownexit subctl was removed in 2.0; use: ownexit direct ${NEW_FORM}")"
fi

CMD=""
LOG_LINES=100
# start --ttl 的原值与秒数；空 = 不自动关闭。
WANT_TTL=""
TTL_SECONDS=""
while [[ $# -gt 0 ]]; do
  case "$1" in
    --host)    HOST="${2:?$(L '--host 需要一个参数' '--host needs a value')}"; shift 2 ;;
    --host=*)  HOST="${1#*=}"; shift ;;
    --port)    SSH_PORT="${2:?$(L '--port 需要一个参数' '--port needs a value')}"; shift 2 ;;
    --port=*)  SSH_PORT="${1#*=}"; shift ;;
    --user)    SSH_USER="${2:?$(L '--user 需要一个参数' '--user needs a value')}"; shift 2 ;;
    --user=*)  SSH_USER="${1#*=}"; shift ;;
    --ttl)     WANT_TTL="${2:?$(L '--ttl 需要一个时长' '--ttl needs a duration')}"; shift 2 ;;
    --ttl=*)   WANT_TTL="${1#*=}"; shift ;;
    help|-h|--help) usage; exit 0 ;;
    login|start|stop|status|qr|devices)
      [[ -z "${CMD}" ]] || die_usage "$(L "只能给一个子命令（已有 ${CMD}）" "Only one subcommand is allowed (already have ${CMD})")"
      CMD="$1"; shift ;;
    log)
      [[ -z "${CMD}" ]] || die_usage "$(L "只能给一个子命令（已有 ${CMD}）" "Only one subcommand is allowed (already have ${CMD})")"
      CMD=log; shift
      if [[ $# -gt 0 && "$1" =~ ^[0-9]+$ ]]; then LOG_LINES="$1"; shift; fi ;;
    *) die_usage "$(L "未知参数或子命令: $1（可用: login / start / stop / status / log / qr / devices / help）" "Unknown option or subcommand: $1 (available: login / start / stop / status / log / qr / devices / help)")" ;;
  esac
done
CMD="${CMD:-login}"
if [[ -n "${WANT_TTL}" ]]; then
  [[ "${CMD}" == start ]] || die_usage "$(L "--ttl 只能跟 start 一起用" "--ttl can only be used with start")"
  TTL_SECONDS="$(parse_ttl "${WANT_TTL}")" || exit 2
  # 不带单位按分钟：提示里补上 m，避免显示成“30 后自动关闭”。
  [[ "${WANT_TTL}" =~ [smh]$ ]] || WANT_TTL="${WANT_TTL}m"
fi

# subctl 不负责首次部署：没有记住的目标时不交互提问，直接提示先跑 setup_direct.sh。
# shellcheck disable=SC2034  # 由 target_lib.sh 的 resolve_target 读取
TARGET_NO_PROMPT_HINT="$(L "还没有记住任何 VPS，请先运行 ownexit direct up --host <ip> 完成部署，或加 --host 指定" "No VPS is remembered yet; run ownexit direct up --host <ip> to deploy first, or pass --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 "$(L "没有 qrencode：macOS 运行 brew install qrencode，Debian/Ubuntu 运行 sudo apt install qrencode" "qrencode not found: on macOS run brew install qrencode, on Debian/Ubuntu run 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 "$(L "本机没有这台 VPS 的订阅，先运行 ownexit direct up --host ${HOST}" "This computer has no subscription for this VPS; run ownexit direct up --host ${HOST} first")"
  qrencode -t ANSIUTF8 < "${NODE_FILE}"
  exit 0
fi
[[ -f "${KEY}" ]] || die "$(L "未找到密钥 ${KEY}，请先运行 ownexit direct up --host ${HOST} 完成部署" "Key ${KEY} not found; run ownexit direct up --host ${HOST} to deploy first")"

# 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)
    # 先清掉上一次的自动关闭计时器，避免旧计时器在新的一次开放中途把服务关掉；带 --ttl 再起新的。
    rcmd "systemctl start '${SUB_SERVICE}'; $(ttl_remote_cmd "${TTL_SECONDS}")"
    if [[ -n "${WANT_TTL}" ]]; then
      echo "$(L "[+] 已启动 ${SUB_SERVICE}，${WANT_TTL} 后自动关闭" "[+] Started ${SUB_SERVICE}; it turns off by itself in ${WANT_TTL}")"
    else
      echo "$(L "[+] 已启动 ${SUB_SERVICE}（临时开；客户端更新完订阅后执行 ownexit direct sub stop 关回）" "[+] Started ${SUB_SERVICE} (temporarily; once clients have updated the subscription, run ownexit direct sub stop)")"
    fi
    ;;
  stop)
    rcmd "$(ttl_remote_cmd); systemctl stop '${SUB_SERVICE}'"
    echo "$(L "[+] 已停止 ${SUB_SERVICE}" "[+] Stopped ${SUB_SERVICE}")"
    ;;
  status)
    rcmd "bash -s" <<REMOTE
if [ -e /etc/systemd/system/ownexit-direct.service ]; then
  echo -n "$(L '代理服务 ownexit-direct : ' 'Proxy service ownexit-direct  : ')"; systemctl is-active ownexit-direct 2>/dev/null || true
elif [ -L /usr/local/bin/sb ]; then
  echo -n "$(L '代理服务（旧版 233boy）  : ' 'Proxy service (old 233boy)    : ')"; systemctl is-active sing-box 2>/dev/null || true
  echo "$(L '  建议运行 ownexit direct migrate 换成本项目的服务（参数不变，客户端不用动）' '  Run ownexit direct migrate to switch to this project service (same settings, clients need nothing)')"
else
  echo "$(L '代理服务               : 未安装' 'Proxy service                 : not installed')"
fi
echo -n "$(L '订阅服务（运行）        : ' 'Subscription service (active) : ')"; systemctl is-active ${SUB_SERVICE} 2>/dev/null || true
echo -n "$(L '订阅服务（开机自启）    : ' 'Subscription service (enabled): ')"; systemctl is-enabled ${SUB_SERVICE} 2>/dev/null || true
# list-timers 一行是「NEXT（星期 日期 时间 时区 4 段）LEFT（如 1h 29min left）…」，只取 LEFT 列。
ttl_line="\$(systemctl list-timers ownexit-subscription-ttl.timer --no-legend 2>/dev/null | sed -n 's/^[^ ]* [^ ]* [^ ]* [^ ]*  *\(.* left\) .*/\1/p' | head -n 1)"
[ -z "\${ttl_line}" ] || echo "$(L '订阅服务自动关闭        : ' 'Subscription auto-off in      : ')\${ttl_line}"
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 "$(L "无法读取 VPS 上的设备表" "Could not read the device list on the 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 "$(L "default : http://${HOST}:${SUB_PORT:-?}/${TOKEN:-?}/sub（自适应；同目录另有 clash.yaml / shadowrocket.txt / sing-box.json / node.txt）" "default : http://${HOST}:${SUB_PORT:-?}/${TOKEN:-?}/sub (adaptive; the same directory also has 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 "$(L "${name} : http://${HOST}:${SUB_PORT:-?}/${tok}/sub（自适应；同目录另有固定格式）" "${name} : http://${HOST}:${SUB_PORT:-?}/${tok}/sub (adaptive; the same directory also has the fixed formats)")"
      else
        echo "$(L "${name} : 本机没有它的订阅（换过电脑？），重跑 ownexit direct --host ${HOST} 生成" "${name} : no subscription for it on this computer (switched computers?); rerun ownexit direct --host ${HOST} to generate it")"
      fi
    done <<< "${names}"
    ;;
esac
