200字
DDNS-GO管理脚本
2025-10-11
2025-10-11
VPS

image

源码:

#!/bin/bash

DDNS_GO_INSTALL_PATH="/opt/ddns-go"
DDNS_GO_SERVICE_FILE="/etc/systemd/system/ddns-go.service"
DDNS_GO_CONFIG_FILE="$DDNS_GO_INSTALL_PATH/.ddns_go_config.yaml"
DDNS_GO_LOG_FILE="$DDNS_GO_INSTALL_PATH/ddns-go.log"
DDNS_GO_UPDATE_LOG_FILE="$DDNS_GO_INSTALL_PATH/update.log"
UPDATE_SCRIPT_NAME="update_ddns_go.sh"
UPDATE_SCRIPT_PATH="$DDNS_GO_INSTALL_PATH/$UPDATE_SCRIPT_NAME"
CRON_COMMENT_TAG="ddns-go-auto-update"

DEFAULT_WEB_PORT="9876"
DDNS_GO_REPO="jeessy2/ddns-go"
DEFAULT_UPDATE_MINUTE="0"
DEFAULT_UPDATE_HOUR="3"
DEFAULT_UPDATE_DAY_OF_WEEK="*"

ARCH="" 

SCRIPT_MANAGED_WEB_PORT="$DEFAULT_WEB_PORT"
SCRIPT_MANAGED_WEB_ENABLED="true"
DDNS_GO_DEFAULT_SYNC_INTERVAL="600" 
SCRIPT_MANAGED_SYNC_INTERVAL="$DDNS_GO_DEFAULT_SYNC_INTERVAL"
SCRIPT_MANAGED_CACHE_TIMES=""    
SCRIPT_MANAGED_SKIP_VERIFY="false" 
SCRIPT_MANAGED_CUSTOM_DNS=""     

BACKED_UP_CONFIG_PATH=""

press_enter_to_continue() {
  echo ""
  read -r -p "按 Enter键 返回主菜单..."
}

check_root() {
  if [ "$(id -u)" -ne 0 ]; then
    echo "错误:此脚本必须以root权限运行。" >&2
    exit 1
  fi
}

get_public_ip() {
    IP=$(curl -s https://ipv4.icanhazip.com || curl -s https://api.ipify.org || curl -s https://ifconfig.me/ip || hostname -I | awk '{print $1}')
    echo "$IP"
}

_read_and_set_current_service_config_vars() {
    SCRIPT_MANAGED_WEB_PORT="$DEFAULT_WEB_PORT"
    SCRIPT_MANAGED_WEB_ENABLED="true"
    SCRIPT_MANAGED_SYNC_INTERVAL="$DDNS_GO_DEFAULT_SYNC_INTERVAL"
    SCRIPT_MANAGED_CACHE_TIMES=""
    SCRIPT_MANAGED_SKIP_VERIFY="false"
    SCRIPT_MANAGED_CUSTOM_DNS=""

    if [ ! -f "$DDNS_GO_SERVICE_FILE" ]; then return ; fi
    local exec_start_line; exec_start_line=$(grep '^ExecStart=' "$DDNS_GO_SERVICE_FILE")
    if [ -z "$exec_start_line" ]; then echo "警告: ddns-go.service 文件中未找到 ExecStart 行。" >&2; return ; fi

    if [[ "$exec_start_line" == *"-noweb"* ]]; then SCRIPT_MANAGED_WEB_ENABLED="false"; else SCRIPT_MANAGED_WEB_ENABLED="true"; fi
    local port_val; port_val=$(echo "$exec_start_line" | grep -oP -- '-l\s*:\s*\K[0-9]+')
    if [[ -n "$port_val" ]]; then SCRIPT_MANAGED_WEB_PORT="$port_val"; elif [ "$SCRIPT_MANAGED_WEB_ENABLED" = "true" ]; then SCRIPT_MANAGED_WEB_PORT="$DEFAULT_WEB_PORT"; fi
    local f_val; f_val=$(echo "$exec_start_line" | grep -oP -- '-f\s+\K[0-9]+'); if [[ -n "$f_val" ]]; then SCRIPT_MANAGED_SYNC_INTERVAL="$f_val"; fi 
    local ct_val; ct_val=$(echo "$exec_start_line" | grep -oP -- '-cacheTimes\s+\K[0-9]+'); if [[ -n "$ct_val" ]]; then SCRIPT_MANAGED_CACHE_TIMES="$ct_val"; fi 
    if [[ "$exec_start_line" == *"-skipVerify"* ]]; then SCRIPT_MANAGED_SKIP_VERIFY="true"; else SCRIPT_MANAGED_SKIP_VERIFY="false"; fi
    local dns_val; dns_val=$(echo "$exec_start_line" | grep -oP -- '-dns\s+\K[^ ]+'); if [[ -n "$dns_val" ]]; then SCRIPT_MANAGED_CUSTOM_DNS="$dns_val"; fi
}

_commit_service_config_and_restart() {
    echo "正在更新 ddns-go 服务配置..."
    mkdir -p "$(dirname "$DDNS_GO_SERVICE_FILE")"
    local new_exec_start_line="ExecStart=$DDNS_GO_INSTALL_PATH/ddns-go"
    if [ "$SCRIPT_MANAGED_WEB_ENABLED" = "true" ]; then new_exec_start_line+=" -l :$SCRIPT_MANAGED_WEB_PORT"; fi
    if [ -n "$SCRIPT_MANAGED_SYNC_INTERVAL" ]; then new_exec_start_line+=" -f $SCRIPT_MANAGED_SYNC_INTERVAL"; fi
    if [ -n "$SCRIPT_MANAGED_CACHE_TIMES" ]; then new_exec_start_line+=" -cacheTimes $SCRIPT_MANAGED_CACHE_TIMES"; fi
    new_exec_start_line+=" -c $DDNS_GO_CONFIG_FILE" 
    if [ "$SCRIPT_MANAGED_SKIP_VERIFY" = "true" ]; then new_exec_start_line+=" -skipVerify"; fi
    if [ -n "$SCRIPT_MANAGED_CUSTOM_DNS" ]; then new_exec_start_line+=" -dns $SCRIPT_MANAGED_CUSTOM_DNS"; fi
    if [ "$SCRIPT_MANAGED_WEB_ENABLED" = "false" ]; then new_exec_start_line+=" -noweb"; fi
    local escaped_new_exec_start_line=$(echo "$new_exec_start_line" | sed -e 's/\\/\\\\/g')
    if [ ! -f "$DDNS_GO_SERVICE_FILE" ]; then
      cat > "$DDNS_GO_SERVICE_FILE" << EOF
[Unit]
Description=DDNS-GO Dynamic DNS Client
Documentation=https://github.com/$DDNS_GO_REPO
After=network.target network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
Group=root
WorkingDirectory=$DDNS_GO_INSTALL_PATH
ExecStart=placeholder
Restart=on-failure
RestartSec=5s
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
      echo "  创建了基础服务文件。"
    fi
    sed -i "\#^ExecStart=#c\\${escaped_new_exec_start_line}" "$DDNS_GO_SERVICE_FILE"
    if [ $? -ne 0 ]; then echo "错误: 更新服务文件 $DDNS_GO_SERVICE_FILE 失败。" >&2; return 1; fi
    echo "  服务文件已更新为: ${new_exec_start_line}"
    systemctl daemon-reload
    if [ $? -ne 0 ]; then echo "错误: systemctl daemon-reload 失败。" >&2; fi
    echo "  启用 ddns-go 服务 (如果尚未启用)..."; systemctl enable ddns-go > /dev/null 2>&1
    echo "  重启 ddns-go 服务..."; systemctl restart ddns-go; local restart_status=$?; sleep 1
    if [ $restart_status -eq 0 ] && systemctl is-active --quiet ddns-go; then echo "  ddns-go 服务已成功配置并 (重)启动。"; else
        echo "错误:(重)启动 ddns-go 服务失败 (退出码: $restart_status)。" >&2
        echo "  请检查服务文件: cat /etc/systemd/system/ddns-go.service" >&2
        echo "  并查看详细日志: journalctl -u ddns-go.service -n 50 --no-pager" >&2
        echo "  以及: systemctl status ddns-go.service --no-pager" >&2
    fi
    _read_and_set_current_service_config_vars ; return $restart_status
}

init_arch() {
  if [ -z "$ARCH" ]; then
    local detected_arch_key=""
    local raw_arch_dpkg=""
    local raw_arch_uname=""

    raw_arch_dpkg=$(dpkg --print-architecture 2>/dev/null)

    if [ -n "$raw_arch_dpkg" ]; then
        case "$raw_arch_dpkg" in
            amd64) detected_arch_key="x86_64" ;;
            arm64) detected_arch_key="arm64" ;;
            armhf) detected_arch_key="armv7" ;; 
            armel) detected_arch_key="armv5" ;; 
            i386) detected_arch_key="i386" ;;
            mips) detected_arch_key="mips" ;;
            mipsel) detected_arch_key="mipsle" ;;
            mips64) detected_arch_key="mips64" ;; 
            mips64el) detected_arch_key="mips64le" ;;
            *) detected_arch_key="$raw_arch_dpkg" ;; 
        esac
    fi

    if [ -z "$detected_arch_key" ]; then 
        raw_arch_uname=$(uname -m)
        case "$raw_arch_uname" in
            x86_64) detected_arch_key="x86_64" ;;
            aarch64) detected_arch_key="arm64" ;;
            armv7l) detected_arch_key="armv7" ;;
            armv6l) detected_arch_key="armv6" ;; 
            armv5tel | armv5l) detected_arch_key="armv5" ;;
            i686) detected_arch_key="i386" ;;
            i386) detected_arch_key="i386" ;;
            mips) detected_arch_key="mips" ;;
            mipsel) detected_arch_key="mipsle" ;;
            mips64) detected_arch_key="mips64" ;; 
            mips64el) detected_arch_key="mips64le" ;;
            *) detected_arch_key="$raw_arch_uname" ;; 
        esac
    fi
    ARCH="$detected_arch_key"

    if [ -z "$ARCH" ]; then
      echo "警告(内部):init_arch 未能确定一个有效的架构关键字。" >&2
    fi
  fi
}

is_ddns_go_installed() {
  if [ -f "$DDNS_GO_INSTALL_PATH/ddns-go" ] && [ -f "$DDNS_GO_SERVICE_FILE" ] && grep -q "ExecStart=$DDNS_GO_INSTALL_PATH/ddns-go" "$DDNS_GO_SERVICE_FILE" 2>/dev/null; then return 0 ; 
  elif [ -f "$DDNS_GO_INSTALL_PATH/ddns-go" ]; then return 0 ; fi
  return 1
}

ensure_dependencies() {
  echo "检查并安装必要的工具 (curl, tar, jq)..."
  NEEDS_INSTALL=0
  for pkg in curl tar jq; do
    if ! dpkg -s "$pkg" > /dev/null 2>&1 && ! command -v "$pkg" > /dev/null 2>&1; then NEEDS_INSTALL=1; echo "  工具 $pkg 未安装。"; break; fi
  done
  if [ $NEEDS_INSTALL -eq 1 ]; then
    echo "  正在更新软件包列表并安装缺失的工具..."
    if command -v apt > /dev/null 2>&1; then apt update > /dev/null 2>&1; if ! apt install -y curl tar jq > /dev/null 2>&1; then echo "错误:使用 apt 安装工具失败。" >&2; return 1; fi
    elif command -v yum > /dev/null 2>&1; then if ! yum install -y curl tar jq > /dev/null 2>&1; then echo "错误:使用 yum 安装工具失败。" >&2; return 1; fi
    elif command -v dnf > /dev/null 2>&1; then if ! dnf install -y curl tar jq > /dev/null 2>&1; then echo "错误:使用 dnf 安装工具失败。" >&2; return 1; fi
    else echo "错误: 未知的包管理器。请手动安装 curl, tar, jq。" >&2; return 1; fi
    echo "  必要的工具已成功安装。"
  else echo "  所有必要的工具均已安装。" ; fi
  return 0
}

uninstall_ddns_go() {
  local preserve_config=${1:-false} 
  local temp_backup_path_for_uninstall="/tmp/.ddns_go_config.yaml.bak-uninstall-$(date +%s)"
  echo "开始彻底卸载 ddns-go..."; BACKED_UP_CONFIG_PATH=""
  if [ "$preserve_config" = "true" ] && [ -f "$DDNS_GO_CONFIG_FILE" ]; then
    echo "  请求保留配置文件。正在备份 $DDNS_GO_CONFIG_FILE 到 $temp_backup_path_for_uninstall..."
    cp "$DDNS_GO_CONFIG_FILE" "$temp_backup_path_for_uninstall"
    if [ $? -ne 0 ]; then echo "  警告: 备份配置文件失败!" >&2; else echo "  配置文件已成功临时备份。"; BACKED_UP_CONFIG_PATH="$temp_backup_path_for_uninstall"; fi
  fi
  SERVICE_BASENAME=$(basename "$DDNS_GO_SERVICE_FILE")
  if systemctl list-unit-files | grep -q "^${SERVICE_BASENAME}"; then echo "  停止并禁用 ddns-go 服务..."; systemctl stop "${SERVICE_BASENAME}" > /dev/null 2>&1; systemctl disable "${SERVICE_BASENAME}" > /dev/null 2>&1;
  else echo "  ddns-go 服务未找到或未激活。" ; fi
  if [ -f "$DDNS_GO_SERVICE_FILE" ]; then echo "  删除 systemd 服务文件..."; rm -f "$DDNS_GO_SERVICE_FILE"; fi
  echo "  重新加载 systemd 配置..."; systemctl daemon-reload >/dev/null 2>&1
  if crontab -l 2>/dev/null | grep -qE "$UPDATE_SCRIPT_PATH|$CRON_COMMENT_TAG"; then echo "  从 crontab 中删除自动更新任务..."; (crontab -l 2>/dev/null | grep -vE "$UPDATE_SCRIPT_PATH|$CRON_COMMENT_TAG") | crontab - ;
  else echo "  未在 crontab 中找到自动更新任务。" ; fi
  OLD_CRON_FILE="/etc/cron.d/ddns-go-update" ; if [ -f "$OLD_CRON_FILE" ]; then echo "  删除旧的 cron 文件 $OLD_CRON_FILE..."; rm -f "$OLD_CRON_FILE"; fi
  if [ -d "$DDNS_GO_INSTALL_PATH" ]; then echo "  删除安装目录: $DDNS_GO_INSTALL_PATH"; rm -rf "$DDNS_GO_INSTALL_PATH"; if [ $? -eq 0 ]; then echo "  目录已成功删除。"; else echo "  错误:删除目录失败。" >&2; fi
  else echo "  安装目录 $DDNS_GO_INSTALL_PATH 未找到。" ; fi
  echo "ddns-go 彻底卸载完成。"
}

install_ddns_go_core() {
  init_arch 
  if [ -z "$ARCH" ]; then echo "错误:无法确定系统架构,无法继续安装核心文件。" >&2; return 1; fi
  echo "正在获取最新的 ddns-go 版本信息 (目标架构: $ARCH)..."
  LATEST_RELEASE_API_URL="https://api.github.com/repos/$DDNS_GO_REPO/releases/latest"
  LATEST_RELEASE_INFO=$(curl -sL --connect-timeout 10 --retry 3 "$LATEST_RELEASE_API_URL")
  if [ -z "$LATEST_RELEASE_INFO" ] || echo "$LATEST_RELEASE_INFO" | jq -e '.message == "Not Found" or .message | test("API rate limit exceeded")' > /dev/null 2>&1 ; then
    echo "错误:无法获取最新版本信息 (API限流或仓库未找到)。响应: $LATEST_RELEASE_INFO" >&2; return 1; fi

  DOWNLOAD_URL=""
  JQ_FILTER_STD='.assets[] | select(.name | test("^ddns-go_.*_linux_" + $arch + "\\.tar\\.gz$")) | .browser_download_url'
  DOWNLOAD_URL=$(echo "$LATEST_RELEASE_INFO" | jq -r --arg arch "$ARCH" "$JQ_FILTER_STD" | head -n 1)

  if [ -z "$DOWNLOAD_URL" ] && [[ "$ARCH" == mips* ]]; then 
    echo "  标准过滤器未找到 '$ARCH' 直匹配文件,尝试MIPS特定后缀..."
    JQ_FILTER_MIPS_HF='.assets[] | select(.name | test("^ddns-go_.*_linux_" + $arch + "_hardfloat\\.tar\\.gz$")) | .browser_download_url'
    DOWNLOAD_URL=$(echo "$LATEST_RELEASE_INFO" | jq -r --arg arch "$ARCH" "$JQ_FILTER_MIPS_HF" | head -n 1)
    if [ -z "$DOWNLOAD_URL" ]; then
      JQ_FILTER_MIPS_SF='.assets[] | select(.name | test("^ddns-go_.*_linux_" + $arch + "_softfloat\\.tar\\.gz$")) | .browser_download_url'
      DOWNLOAD_URL=$(echo "$LATEST_RELEASE_INFO" | jq -r --arg arch "$ARCH" "$JQ_FILTER_MIPS_SF" | head -n 1)
      if [ -n "$DOWNLOAD_URL" ]; then echo "  找到MIPS Softfloat版本。" ; fi
    elif [ -n "$DOWNLOAD_URL" ]; then echo "  找到MIPS Hardfloat版本。" ; fi
  fi
  
  if [ -z "$DOWNLOAD_URL" ]; then
    echo "  主要过滤器未找到匹配,尝试通用后备过滤器..."
    JQ_FILTER_FALLBACK='.assets[] | select(.name | test("linux_" + $arch + "\\.tar\\.gz$") and (.name | contains("ddns-go"))) | .browser_download_url'
    DOWNLOAD_URL=$(echo "$LATEST_RELEASE_INFO" | jq -r --arg arch "$ARCH" "$JQ_FILTER_FALLBACK" | head -n 1)

    if [ -z "$DOWNLOAD_URL" ] && [[ "$ARCH" == mips* ]]; then 
        echo "  通用后备过滤器未找到 '$ARCH' 直匹配文件,尝试MIPS特定后缀的后备过滤器..."
        JQ_FILTER_MIPS_FALLBACK_HF='.assets[] | select(.name | test("linux_" + $arch + "_hardfloat\\.tar\\.gz$") and (.name | contains("ddns-go"))) | .browser_download_url'
        DOWNLOAD_URL=$(echo "$LATEST_RELEASE_INFO" | jq -r --arg arch "$ARCH" "$JQ_FILTER_MIPS_FALLBACK_HF" | head -n 1)
        if [ -z "$DOWNLOAD_URL" ]; then
            JQ_FILTER_MIPS_FALLBACK_SF='.assets[] | select(.name | test("linux_" + $arch + "_softfloat\\.tar\\.gz$") and (.name | contains("ddns-go"))) | .browser_download_url'
            DOWNLOAD_URL=$(echo "$LATEST_RELEASE_INFO" | jq -r --arg arch "$ARCH" "$JQ_FILTER_MIPS_FALLBACK_SF" | head -n 1)
            if [ -n "$DOWNLOAD_URL" ]; then echo "  找到MIPS Softfloat版本 (后备)。" ; fi
        elif [ -n "$DOWNLOAD_URL" ]; then echo "  找到MIPS Hardfloat版本 (后备)。" ; fi
    fi
  fi

  if [ -z "$DOWNLOAD_URL" ]; then echo "错误:未找到适用于 linux $ARCH 架构的 ddns-go 下载链接。可用资源:" >&2; echo "$LATEST_RELEASE_INFO" | jq -r '.assets[].name' >&2; return 1; fi
  echo "  找到下载链接:$DOWNLOAD_URL"
  LATEST_VERSION_TAG=$(echo "$LATEST_RELEASE_INFO" | jq -r '.tag_name'); LATEST_VERSION=${LATEST_VERSION_TAG#v}
  if [ -z "$LATEST_VERSION" ]; then echo "错误: 无法解析最新版本标签。" >&2; return 1; fi
  echo "  准备安装 ddns-go 版本 $LATEST_VERSION..."; mkdir -p "$DDNS_GO_INSTALL_PATH"; cd "$DDNS_GO_INSTALL_PATH" || { echo "错误:无法进入目录"; return 1; }
  echo "  正在下载 ddns-go..."; curl -L -o ddns-go.tar.gz "$DOWNLOAD_URL" --connect-timeout 20 --retry 3
  if [ $? -ne 0 ]; then echo "错误:下载 ddns-go 失败。" >&2; return 1; fi
  echo "  正在解压 ddns-go..."; rm -f "$DDNS_GO_INSTALL_PATH/ddns-go" 
  tar -xzf ddns-go.tar.gz -C "$DDNS_GO_INSTALL_PATH" ddns-go 2>/dev/null
  if [ ! -f "$DDNS_GO_INSTALL_PATH/ddns-go" ]; then
      BINARY_PATH_IN_ARCHIVE=$(tar -tzf ddns-go.tar.gz | grep -E '/ddns-go$|^ddns-go$' | head -n 1)
      if [ -n "$BINARY_PATH_IN_ARCHIVE" ]; then echo "  检测到二进制文件位于: $BINARY_PATH_IN_ARCHIVE, 尝试提取..."; tar -xzf ddns-go.tar.gz -C "$DDNS_GO_INSTALL_PATH" --strip-components=$(echo "$BINARY_PATH_IN_ARCHIVE" | awk -F/ '{print NF-1}') "$BINARY_PATH_IN_ARCHIVE"; fi
  fi
  if [ ! -f "$DDNS_GO_INSTALL_PATH/ddns-go" ]; then echo "错误:解压失败,未找到执行文件。" >&2; rm -f ddns-go.tar.gz; return 1; fi
  rm -f ddns-go.tar.gz; chmod +x "$DDNS_GO_INSTALL_PATH/ddns-go"; echo "ddns-go 版本 $LATEST_VERSION 安装成功。"
  return 0
}

_configure_auto_update_cronjob() {
  local minute="$1"; local hour="$2"; local day_of_week="$3"
  init_arch 
  if [ -z "$ARCH" ]; then echo "错误(配置更新): 无法确定架构。"; return 1; fi
  mkdir -p "$(dirname "$UPDATE_SCRIPT_PATH")"
cat > "$UPDATE_SCRIPT_PATH" << EOF
#!/bin/bash
LOG_FILE="$DDNS_GO_UPDATE_LOG_FILE"
INSTALL_PATH="$DDNS_GO_INSTALL_PATH"
DDNS_GO_EXEC="\$INSTALL_PATH/ddns-go"
REPO="$DDNS_GO_REPO"

get_target_arch_for_update() {
  local detected_arch_key=""
  local raw_arch_dpkg=\$(dpkg --print-architecture 2>/dev/null)
  if [ -n "\$raw_arch_dpkg" ]; then
      case "\$raw_arch_dpkg" in
          amd64) detected_arch_key="x86_64" ;; arm64) detected_arch_key="arm64" ;;
          armhf) detected_arch_key="armv7" ;; armel) detected_arch_key="armv5" ;;
          i386) detected_arch_key="i386" ;; mips) detected_arch_key="mips" ;;
          mipsel) detected_arch_key="mipsle" ;; mips64) detected_arch_key="mips64" ;;
          mips64el) detected_arch_key="mips64le" ;; *) detected_arch_key="\$raw_arch_dpkg" ;;
      esac
  fi
  if [ -z "\$detected_arch_key" ]; then 
      local raw_arch_uname=\$(uname -m)
      case "\$raw_arch_uname" in
          x86_64) detected_arch_key="x86_64" ;; aarch64) detected_arch_key="arm64" ;;
          armv7l) detected_arch_key="armv7" ;; armv6l) detected_arch_key="armv6" ;;
          armv5tel | armv5l) detected_arch_key="armv5" ;;
          i686) detected_arch_key="i386" ;; i386) detected_arch_key="i386" ;;
          mips) detected_arch_key="mips" ;; mipsel) detected_arch_key="mipsle" ;;
          mips64) detected_arch_key="mips64" ;; mips64el) detected_arch_key="mips64le" ;;
          *) detected_arch_key="\$raw_arch_uname" ;; 
      esac
  fi
  echo "\$detected_arch_key"
}
TARGET_ARCH=\$(get_target_arch_for_update)
if [ -z "\$TARGET_ARCH" ]; then printf "错误(更新脚本): 无法确定目标架构。\n"; echo "错误(更新脚本): 无法确定目标架构。" >> \$LOG_FILE; exit 1; fi

echo "----------------------------------------------------" >> \$LOG_FILE
echo "开始检查 ddns-go 更新 (\$(date)) for arch \$TARGET_ARCH" >> \$LOG_FILE
if [ ! -x "\$DDNS_GO_EXEC" ]; then printf "错误: ddns-go 执行文件 %s 未找到或不可执行。\n" "\$DDNS_GO_EXEC"; echo "错误: \$DDNS_GO_EXEC 未找到或不可执行。" >> \$LOG_FILE; exit 1; fi

CURRENT_VERSION_RAW=\$("\$DDNS_GO_EXEC" -v 2>&1 | grep -oE '[v]?[0-9]+\.[0-9]+\.[0-9]+' | head -n 1)
if [ -z "\$CURRENT_VERSION_RAW" ]; then printf "错误: 无法从 %s -v 的输出解析当前版本号。\n" "\$DDNS_GO_EXEC"; echo "错误: 无法解析当前版本号。" >> \$LOG_FILE; exit 1; fi
CURRENT_VERSION=\${CURRENT_VERSION_RAW#v}
echo "ddns-go -v command output for version check: [\$CURRENT_VERSION_RAW]" >> \$LOG_FILE

LATEST_RELEASE_API_URL="https://api.github.com/repos/\$REPO/releases/latest"
LATEST_RELEASE_INFO=\$(curl -sL --connect-timeout 10 --retry 3 "\$LATEST_RELEASE_API_URL")
if [ -z "\$LATEST_RELEASE_INFO" ] || echo "\$LATEST_RELEASE_INFO" | jq -e '.message=="Not Found" or .message|test("API rate limit exceeded")' >/dev/null 2>&1; then
  printf "错误: 无法获取最新的 ddns-go 版本信息 (可能是API限流或仓库不存在)。\n"
  echo "错误: 无法获取最新版本信息. API响应: \$LATEST_RELEASE_INFO" >> \$LOG_FILE; exit 1; fi

DOWNLOAD_URL=""
JQ_FILTER_STD='.assets[] | select(.name | test("^ddns-go_.*_linux_" + \$arch + "\\\\.tar\\\\.gz\$")) | .browser_download_url'
DOWNLOAD_URL=\$(echo "\$LATEST_RELEASE_INFO" | jq -r --arg arch "\$TARGET_ARCH" "\$JQ_FILTER_STD" | head -n 1)

if [ -z "\$DOWNLOAD_URL" ] && [[ "\$TARGET_ARCH" == mips* ]]; then
  echo "  (日志) 标准过滤器未找到 '\$TARGET_ARCH' 直匹配文件,尝试MIPS特定后缀..." >> \$LOG_FILE
  JQ_FILTER_MIPS_HF='.assets[] | select(.name | test("^ddns-go_.*_linux_" + \$arch + "_hardfloat\\\\.tar\\\\.gz\$")) | .browser_download_url'
  DOWNLOAD_URL=\$(echo "\$LATEST_RELEASE_INFO" | jq -r --arg arch "\$TARGET_ARCH" "\$JQ_FILTER_MIPS_HF" | head -n 1)
  if [ -z "\$DOWNLOAD_URL" ]; then
    JQ_FILTER_MIPS_SF='.assets[] | select(.name | test("^ddns-go_.*_linux_" + \$arch + "_softfloat\\\\.tar\\\\.gz\$")) | .browser_download_url'
    DOWNLOAD_URL=\$(echo "\$LATEST_RELEASE_INFO" | jq -r --arg arch "\$TARGET_ARCH" "\$JQ_FILTER_MIPS_SF" | head -n 1)
  fi
fi

if [ -z "\$DOWNLOAD_URL" ]; then
  echo "  (日志) 主要过滤器未找到匹配,尝试通用后备过滤器..." >> \$LOG_FILE
  JQ_FILTER_FALLBACK='.assets[] | select(.name | test("linux_" + \$arch + "\\\\.tar\\\\.gz\$") and (.name | contains("ddns-go"))) | .browser_download_url'
  DOWNLOAD_URL=\$(echo "\$LATEST_RELEASE_INFO" | jq -r --arg arch "\$TARGET_ARCH" "\$JQ_FILTER_FALLBACK" | head -n 1)
  if [ -z "\$DOWNLOAD_URL" ] && [[ "\$TARGET_ARCH" == mips* ]]; then
      echo "  (日志) MIPS通用后备过滤器未找到,尝试MIPS特定后缀的后备..." >> \$LOG_FILE
      JQ_FILTER_MIPS_FALLBACK_HF='.assets[] | select(.name | test("linux_" + \$arch + "_hardfloat\\\\.tar\\\\.gz\$") and (.name | contains("ddns-go"))) | .browser_download_url'
      DOWNLOAD_URL=\$(echo "\$LATEST_RELEASE_INFO" | jq -r --arg arch "\$TARGET_ARCH" "\$JQ_FILTER_MIPS_FALLBACK_HF" | head -n 1)
      if [ -z "\$DOWNLOAD_URL" ]; then
          JQ_FILTER_MIPS_FALLBACK_SF='.assets[] | select(.name | test("linux_" + \$arch + "_softfloat\\\\.tar\\\\.gz\$") and (.name | contains("ddns-go"))) | .browser_download_url'
          DOWNLOAD_URL=\$(echo "\$LATEST_RELEASE_INFO" | jq -r --arg arch "\$TARGET_ARCH" "\$JQ_FILTER_MIPS_FALLBACK_SF" | head -n 1)
      fi
  fi
fi

if [ -z "\$DOWNLOAD_URL" ]; then printf "错误: 未找到适用于 %s 架构的 ddns-go 下载链接。\n" "\$TARGET_ARCH"; echo "错误: 未找到适用于 \$TARGET_ARCH 的下载链接。" >> \$LOG_FILE; echo "可用资源:" >> \$LOG_FILE; echo "\$LATEST_RELEASE_INFO" | jq -r '.assets[].name' >> \$LOG_FILE; exit 1; fi
LATEST_VERSION_TAG=\$(echo "\$LATEST_RELEASE_INFO" | jq -r '.tag_name'); LATEST_VERSION=\${LATEST_VERSION_TAG#v}
if [ -z "\$LATEST_VERSION" ]; then printf "错误: 无法从API响应中解析最新版本标签。\n"; echo "错误: 无法解析最新版本标签。" >> \$LOG_FILE; exit 1; fi

echo "当前版本: \$CURRENT_VERSION, 最新版本: \$LATEST_VERSION" >> \$LOG_FILE
if [ "\$CURRENT_VERSION" == "\$LATEST_VERSION" ]; then 
  printf "ddns-go 程序已是最新版本 (v%s)。\n" "\$CURRENT_VERSION"
  echo "ddns-go 已是最新版 (v\$CURRENT_VERSION)。" >> \$LOG_FILE; 
  if ! systemctl is-active --quiet ddns-go && [ -f "/etc/systemd/system/ddns-go.service" ]; then 
    echo "  服务未运行,尝试启动..." >> \$LOG_FILE; 
    systemctl start ddns-go >> \$LOG_FILE 2>&1; 
  fi; 
  exit 0; 
fi

printf "发现新版 ddns-go v%s (当前 v%s),开始更新...\n" "\$LATEST_VERSION" "\$CURRENT_VERSION"
echo "发现新版 v\$LATEST_VERSION (当前 v\$CURRENT_VERSION),开始更新..." >> \$LOG_FILE

if systemctl is-active --quiet ddns-go; then 
  echo "  正在停止 ddns-go 服务..." >> \$LOG_FILE; 
  systemctl stop ddns-go >> \$LOG_FILE 2>&1; 
  if [ \$? -ne 0 ]; then echo "警告: 停止服务失败,仍尝试更新。" >> \$LOG_FILE; else echo "  服务已停止。" >> \$LOG_FILE; fi
fi
cd "\$INSTALL_PATH" || { printf "错误:无法进入安装目录 %s\n" "\$INSTALL_PATH"; echo "错误: 无法进入目录 \$INSTALL_PATH" >> \$LOG_FILE; exit 1; }

if [ -f "\$DDNS_GO_EXEC" ]; then 
  echo "  备份当前 ddns-go 执行文件..." >> \$LOG_FILE;
  mv "\$DDNS_GO_EXEC" "\$DDNS_GO_EXEC.old" >> \$LOG_FILE 2>&1; 
fi

echo "  正在下载新版本从 \$DOWNLOAD_URL..." >> \$LOG_FILE 
curl -L -o ddns-go.tar.gz "\$DOWNLOAD_URL" --connect-timeout 20 --retry 3
if [ \$? -ne 0 ]; then 
  printf "错误:下载新版本 ddns-go 失败。\n"
  echo "错误: 下载失败。" >> \$LOG_FILE; 
  if [ -f "\$DDNS_GO_EXEC.old" ]; then echo "  尝试恢复旧版本..." >> \$LOG_FILE; mv "\$DDNS_GO_EXEC.old" "\$DDNS_GO_EXEC" >> \$LOG_FILE 2>&1; fi; 
  echo "  尝试重启 ddns-go 服务(下载失败后)..." >> \$LOG_FILE; systemctl start ddns-go >> \$LOG_FILE 2>&1; exit 1; 
fi

echo "  正在解压新版本..." >> \$LOG_FILE 
rm -f "\$INSTALL_PATH/ddns-go"
tar -xzf ddns-go.tar.gz -C "\$INSTALL_PATH" ddns-go 2>/dev/null
if [ ! -f "\$INSTALL_PATH/ddns-go" ]; then
    BINARY_PATH_IN_ARCHIVE=\$(tar -tzf ddns-go.tar.gz | grep -E '/ddns-go$|^ddns-go$' | head -n 1)
    if [ -n "\$BINARY_PATH_IN_ARCHIVE" ]; then 
      echo "  检测到压缩包内路径: \$BINARY_PATH_IN_ARCHIVE, 尝试提取..." >> \$LOG_FILE
      tar -xzf ddns-go.tar.gz -C "\$INSTALL_PATH" --strip-components=\$(echo "\$BINARY_PATH_IN_ARCHIVE" | awk -F/ '{print NF-1}') "\$BINARY_PATH_IN_ARCHIVE" >> \$LOG_FILE 2>&1; 
    fi
fi

if [ ! -f "\$DDNS_GO_EXEC" ]; then 
  printf "错误:解压新版本 ddns-go 后未找到执行文件。\n"
  echo "错误: 解压后未找到执行文件。" >> \$LOG_FILE; rm -f ddns-go.tar.gz; 
  if [ -f "\$DDNS_GO_EXEC.old" ]; then echo "  尝试恢复旧版本..." >> \$LOG_FILE; mv "\$DDNS_GO_EXEC.old" "\$DDNS_GO_EXEC" >> \$LOG_FILE 2>&1; fi; 
  echo "  尝试重启 ddns-go 服务(解压失败后)..." >> \$LOG_FILE; systemctl start ddns-go >> \$LOG_FILE 2>&1; exit 1; 
fi
rm -f ddns-go.tar.gz; chmod +x "\$DDNS_GO_EXEC"

NEW_INSTALLED_VERSION_RAW=\$("\$DDNS_GO_EXEC" -v 2>&1 | grep -oE '[v]?[0-9]+\.[0-9]+\.[0-9]+' | head -n 1)
NEW_INSTALLED_VERSION=\${NEW_INSTALLED_VERSION_RAW#v}

printf "ddns-go 已成功更新到版本 v%s。正在重启服务...\n" "\$NEW_INSTALLED_VERSION"
echo "更新完成。新版本已安装 (v\$NEW_INSTALLED_VERSION)。正在重启服务..." >> \$LOG_FILE

systemctl start ddns-go >> \$LOG_FILE 2>&1 
if systemctl is-active --quiet ddns-go; then 
  echo "服务已成功重启。" >> \$LOG_FILE; 
  if [ -f "\$DDNS_GO_EXEC.old" ]; then rm -f "\$DDNS_GO_EXEC.old"; fi
else 
  printf "错误:ddns-go 更新后服务重启失败。\n"
  echo "错误: 服务重启失败。" >> \$LOG_FILE; 
  if [ -f "\$DDNS_GO_EXEC.old" ]; then echo "  检测到旧版本备份 \$DDNS_GO_EXEC.old,可尝试手动恢复。" >> \$LOG_FILE; fi; 
fi
exit 0
EOF
  chmod +x "$UPDATE_SCRIPT_PATH"
  (crontab -l 2>/dev/null | grep -vF "$UPDATE_SCRIPT_PATH" | grep -vF "$CRON_COMMENT_TAG") | crontab -
  CRON_JOB_LINE="${minute} ${hour} * * ${day_of_week} $UPDATE_SCRIPT_PATH >> \"$DDNS_GO_UPDATE_LOG_FILE\" 2>&1" 
  (crontab -l 2>/dev/null ; echo "$CRON_JOB_LINE # $CRON_COMMENT_TAG") | crontab - 
  local schedule_desc="每天 ${hour}:${minute}"; if [ "$day_of_week" != "*" ]; then local days=("周日" "周一" "周二" "周三" "周四" "周五" "周六"); if [[ "$day_of_week" =~ ^[0-6]$ ]]; then schedule_desc="每周${days[$day_of_week]} ${hour}:${minute}"; else schedule_desc="每周 (无效星期: $day_of_week) ${hour}:${minute}"; fi; fi
  echo "  自动更新任务已配置为 ${schedule_desc} 执行。"
  echo "  更新脚本: $UPDATE_SCRIPT_PATH"; echo "  更新日志: $DDNS_GO_UPDATE_LOG_FILE"
  return 0
}

handle_install() {
  echo "--- 安装 ddns-go ---"
  if is_ddns_go_installed; then 
    echo "ddns-go 已安装。建议使用“重新安装 ddns-go”选项来更新或修复安装。"
    echo "“重新安装”选项会询问您是否保留现有的 YAML 配置文件和特定服务参数。"
    local choice_reinstall; read -r -p "是否要切换到“重新安装”流程? (Y/n): " choice_reinstall
    if [[ "$choice_reinstall" =~ ^[Yy]$ ]] || [ -z "$choice_reinstall" ]; then handle_reinstall; return; fi
    echo "选择不进行重新安装。如果您希望进行全新的覆盖安装(不保留 YAML 配置文件),请确认。"
    local choice_fresh; read -r -p "继续进行全新安装 (将删除现有服务和配置文件)? (y/N): " choice_fresh
    if ! [[ "$choice_fresh" =~ ^[Yy]$ ]]; then echo "安装已取消。"; return; fi
    echo "正在准备全新安装..."; uninstall_ddns_go false 
  fi
  if ! ensure_dependencies; then return 1; fi
  SCRIPT_MANAGED_WEB_PORT="$DEFAULT_WEB_PORT"; SCRIPT_MANAGED_WEB_ENABLED="true"
  SCRIPT_MANAGED_SYNC_INTERVAL="$DDNS_GO_DEFAULT_SYNC_INTERVAL"; SCRIPT_MANAGED_CACHE_TIMES=""
  SCRIPT_MANAGED_SKIP_VERIFY="false"; SCRIPT_MANAGED_CUSTOM_DNS=""
  local port_input; read -r -p "请输入 ddns-go Web 服务端口号 (1-65535, 回车默认 $SCRIPT_MANAGED_WEB_PORT): " port_input
  SCRIPT_MANAGED_WEB_PORT=${port_input:-$SCRIPT_MANAGED_WEB_PORT} 
  if ! [[ "$SCRIPT_MANAGED_WEB_PORT" =~ ^[0-9]+$ && "$SCRIPT_MANAGED_WEB_PORT" -ge 1 && "$SCRIPT_MANAGED_WEB_PORT" -le 65535 ]]; then echo "错误:无效端口。使用默认 $DEFAULT_WEB_PORT。"; SCRIPT_MANAGED_WEB_PORT="$DEFAULT_WEB_PORT"; fi
  if ! install_ddns_go_core; then return 1; fi; if ! _commit_service_config_and_restart; then return 1; fi
  echo "配置默认自动更新任务..."; if ! _configure_auto_update_cronjob "$DEFAULT_UPDATE_MINUTE" "$DEFAULT_UPDATE_HOUR" "$DEFAULT_UPDATE_DAY_OF_WEEK"; then echo "警告:自动更新配置失败。"; fi
  echo "----------------------------------------------------"; echo "ddns-go 安装和初始配置完成! Web 服务端口 $SCRIPT_MANAGED_WEB_PORT。"; echo "请通过 Web 界面 http://<您的IP>:$SCRIPT_MANAGED_WEB_PORT 配置域名和DNS服务商。"; handle_status
}

handle_reinstall() {
  echo "--- 重新安装 ddns-go ---"
  local preserve_yaml_choice="y"; local preserve_service_params_choice="y"; local proceed_due_to_not_installed=false 
  if is_ddns_go_installed; then 
      read -r -p "是否保留 YAML 配置文件 (.ddns_go_config.yaml)?此文件主要包含您的【域名列表、DNS服务商(如阿里云/腾讯云/Cloudflare等)的API密钥、Webhook通知、TTL值】等核心DDNS配置。(Y/n): " yaml_in
      preserve_yaml_choice=${yaml_in:-Y} 
      read -r -p "是否保留服务启动参数 (如Web端口, 同步间隔等 - 来自 .service 文件)? (Y/n): " service_in
      preserve_service_params_choice=${service_in:-Y} 
  else echo "未检测到已安装。将按全新安装流程处理配置。"; preserve_yaml_choice="n"; preserve_service_params_choice="n"; proceed_due_to_not_installed=true; fi

  if [[ "$preserve_service_params_choice" =~ ^[Yy]$ ]]; then if [ "$proceed_due_to_not_installed" = "false" ]; then echo "尝试加载并保留现有服务参数..."; fi; _read_and_set_current_service_config_vars ;
  else echo "使用脚本默认服务参数..."; SCRIPT_MANAGED_WEB_PORT="$DEFAULT_WEB_PORT"; SCRIPT_MANAGED_WEB_ENABLED="true"; SCRIPT_MANAGED_SYNC_INTERVAL="$DDNS_GO_DEFAULT_SYNC_INTERVAL"; SCRIPT_MANAGED_CACHE_TIMES=""; SCRIPT_MANAGED_SKIP_VERIFY="false"; SCRIPT_MANAGED_CUSTOM_DNS="";
    if [ "$SCRIPT_MANAGED_WEB_ENABLED" = "true" ]; then local port_in_re; read -r -p "请输入Web端口 (新服务配置, 1-65535, 回车默认 $SCRIPT_MANAGED_WEB_PORT): " port_in_re; SCRIPT_MANAGED_WEB_PORT=${port_in_re:-$SCRIPT_MANAGED_WEB_PORT}; if ! [[ "$SCRIPT_MANAGED_WEB_PORT" =~ ^[0-9]+$ && "$SCRIPT_MANAGED_WEB_PORT" -ge 1 && "$SCRIPT_MANAGED_WEB_PORT" -le 65535 ]]; then echo "错误:无效端口。使用默认$DEFAULT_WEB_PORT。"; SCRIPT_MANAGED_WEB_PORT="$DEFAULT_WEB_PORT"; fi; fi; fi
  if [ "$proceed_due_to_not_installed" = "false" ]; then if [[ "$preserve_yaml_choice" =~ ^[Yy]$ ]]; then echo "卸载旧版 (尝试保留YAML)..."; uninstall_ddns_go true; else echo "卸载旧版 (不保留YAML)..."; uninstall_ddns_go false; fi; echo ""; else BACKED_UP_CONFIG_PATH=""; fi
  if ! ensure_dependencies; then return 1; fi; if ! install_ddns_go_core; then if [ -n "$BACKED_UP_CONFIG_PATH" ] && [ -f "$BACKED_UP_CONFIG_PATH" ]; then echo "错误:核心文件安装失败。备份的YAML仍在: $BACKED_UP_CONFIG_PATH" >&2; fi; return 1; fi
  if [[ "$preserve_yaml_choice" =~ ^[Yy]$ ]]; then if [ -n "$BACKED_UP_CONFIG_PATH" ] && [ -f "$BACKED_UP_CONFIG_PATH" ]; then echo "  恢复YAML配置..."; mkdir -p "$(dirname "$DDNS_GO_CONFIG_FILE")"; cp "$BACKED_UP_CONFIG_PATH" "$DDNS_GO_CONFIG_FILE"; if [ $? -eq 0 ]; then echo "  YAML恢复成功。"; rm -f "$BACKED_UP_CONFIG_PATH"; else echo "  错误:YAML恢复失败! 文件仍在 $BACKED_UP_CONFIG_PATH" >&2; fi; elif [ "$proceed_due_to_not_installed" = "false" ]; then echo "  选择保留YAML但未找到有效备份。"; fi
  else echo "  选择不保留YAML。"; if [ -n "$BACKED_UP_CONFIG_PATH" ] && [ -f "$BACKED_UP_CONFIG_PATH" ]; then rm -f "$BACKED_UP_CONFIG_PATH"; fi; BACKED_UP_CONFIG_PATH=""; fi
  if ! _commit_service_config_and_restart; then return 1; fi
  echo "检查自动更新任务..."; local cron_exists; if crontab -l 2>/dev/null | grep -qF "$UPDATE_SCRIPT_PATH"; then cron_exists="true"; else cron_exists="false"; fi
  if [ "$cron_exists" = "false" ]; then echo "  未找到更新任务,配置默认。"; if ! _configure_auto_update_cronjob "$DEFAULT_UPDATE_MINUTE" "$DEFAULT_UPDATE_HOUR" "$DEFAULT_UPDATE_DAY_OF_WEEK"; then echo "警告:自动更新配置失败。"; fi; else echo "  检测到已存在更新任务,跳过。"; fi
  echo "----------------------------------------------------"; echo "ddns-go 重新安装完成!"; if [[ "$preserve_yaml_choice" =~ ^[Yy]$ ]]; then echo "  - YAML配置文件已尝试恢复。"; else echo "  - YAML配置文件未恢复。"; fi; if [[ "$preserve_service_params_choice" =~ ^[Yy]$ ]]; then echo "  - 服务参数已从旧配置或默认值应用。"; else echo "  - 服务参数已使用脚本默认值应用。"; fi; echo "  - 请检查状态及Web界面 (端口: $SCRIPT_MANAGED_WEB_PORT)。"; handle_status; BACKED_UP_CONFIG_PATH="" 
}

handle_uninstall() {
  echo "--- 彻底卸载 ddns-go ---"; if ! is_ddns_go_installed; then echo "ddns-go 未安装。"; return; fi
  read -r -p "这将彻底删除 ddns-go 及其所有配置和日志 (YAML不保留)。确定吗? (y/N): " choice
  if [[ "$choice" =~ ^[Yy]$ ]]; then uninstall_ddns_go false; else echo "卸载已取消。"; fi
}

handle_status() {
  echo "--- ddns-go 状态检查 ---"; _read_and_set_current_service_config_vars 
  if ! is_ddns_go_installed && ! [ -f "$DDNS_GO_INSTALL_PATH/ddns-go" ]; then echo "ddns-go 未安装或程序文件不存在。"; return; fi
  SERVICE_BASENAME=$(basename "$DDNS_GO_SERVICE_FILE"); local web_access_url_status="Web服务配置异常或已禁用"
  if [ "$SCRIPT_MANAGED_WEB_ENABLED" = "true" ]; then SERVER_IP_STATUS=$(get_public_ip); if [ -z "$SERVER_IP_STATUS" ]; then SERVER_IP_STATUS="<你的IP>"; fi; web_access_url_status="http://${SERVER_IP_STATUS}:${SCRIPT_MANAGED_WEB_PORT}"; fi
  echo "Web 界面访问 (基于服务配置): $web_access_url_status"
  if [ -f "$DDNS_GO_SERVICE_FILE" ] && systemctl list-unit-files | grep -q "^${SERVICE_BASENAME}"; then
    echo "服务状态 ($SERVICE_BASENAME):"; systemctl status "$SERVICE_BASENAME" --no-pager -n 15; echo ""
    if systemctl is-active --quiet "$SERVICE_BASENAME"; then local running_cmd; running_cmd=$(systemctl show -p MainPID ${SERVICE_BASENAME} | sed 's/MainPID=//' | xargs -I {} --no-run-if-empty ps -o cmd= -p {} 2>/dev/null || ps -o cmd= -p $(systemctl show -p MainPID ${SERVICE_BASENAME} | sed 's/MainPID=//') 2>/dev/null) ; echo "服务正在运行。"; if [ -n "$running_cmd" ]; then echo "  实际运行命令: $running_cmd"; else echo "  无法获取实际运行命令。"; fi
    else echo "服务未在运行。" ; fi
  elif [ -f "$DDNS_GO_INSTALL_PATH/ddns-go" ]; then echo "服务文件($DDNS_GO_SERVICE_FILE)未找到,但程序存在。可能安装不完整。"; else echo "ddns-go程序或服务文件均未找到。"; fi
  echo "YAML配置文件: $DDNS_GO_CONFIG_FILE"; if [ -f "$DDNS_GO_CONFIG_FILE" ]; then echo "  状态: 存在"; else echo "  状态: 未找到"; fi
  echo "程序日志: $DDNS_GO_LOG_FILE"; echo "安装路径: $DDNS_GO_INSTALL_PATH"
  echo "程序自动更新状态:"; if [ -f "$UPDATE_SCRIPT_PATH" ]; then echo "  更新脚本: $UPDATE_SCRIPT_PATH"; CRON_JOB=$(crontab -l 2>/dev/null | grep -F "$UPDATE_SCRIPT_PATH" | grep -F "$CRON_COMMENT_TAG"); if [ -n "$CRON_JOB" ]; then echo "  定时任务: $CRON_JOB"; else echo "  未找到自动更新任务。"; fi; echo "  更新日志: $DDNS_GO_UPDATE_LOG_FILE"; else echo "  自动更新脚本未配置。"; fi
}

handle_set_sync_interval() {
  _read_and_set_current_service_config_vars; echo "--- 配置同步间隔 (-f) ---"; echo "当前: ${SCRIPT_MANAGED_SYNC_INTERVAL} 秒"
  read -r -p "新间隔 (秒, 例如300, 回车不改): " interval_input
  if [ -n "$interval_input" ]; then if [[ "$interval_input" =~ ^[0-9]+$ && "$interval_input" -gt 0 ]]; then SCRIPT_MANAGED_SYNC_INTERVAL="$interval_input"; if _commit_service_config_and_restart; then echo "间隔更新为 ${SCRIPT_MANAGED_SYNC_INTERVAL} 秒。"; else echo "错误:更新失败。"; fi; else echo "错误:无效输入。"; fi; else echo "未修改。"; fi
}

handle_set_cache_times() {
  _read_and_set_current_service_config_vars; echo "--- 配置比对频率 (-cacheTimes) ---"; echo "当前: ${SCRIPT_MANAGED_CACHE_TIMES:-未设置}"
  read -r -p "新频率 (整数, 或 'clear' 清除, 回车不改): " cache_input
  if [ -n "$cache_input" ]; then if [[ "$cache_input" =~ ^[0-9]+$ && "$cache_input" -ge 0 ]]; then SCRIPT_MANAGED_CACHE_TIMES="$cache_input"; if _commit_service_config_and_restart; then echo "频率更新为 ${SCRIPT_MANAGED_CACHE_TIMES}。"; else echo "错误:更新失败。"; fi
  elif [ "$cache_input" == "clear" ]; then SCRIPT_MANAGED_CACHE_TIMES=""; if _commit_service_config_and_restart; then echo "频率设置已清除。"; else echo "错误:清除失败。"; fi; else echo "错误:无效输入。"; fi; else echo "未修改。"; fi
}

handle_toggle_skip_verify() {
  _read_and_set_current_service_config_vars; echo "--- 配置跳过TLS验证 (-skipVerify) ---"
  local cur_stat="关闭 (执行验证)"; if [ "$SCRIPT_MANAGED_SKIP_VERIFY" = "true" ]; then cur_stat="开启 (跳过验证)"; fi; echo "当前: $cur_stat"
  local choice_txt="开启 (跳过验证)"; if [ "$SCRIPT_MANAGED_SKIP_VERIFY" = "true" ]; then choice_txt="关闭 (执行验证)"; fi
  read -r -p "切换为 '$choice_txt' 吗? (y/N): " choice
  if [[ "$choice" =~ ^[Yy]$ ]]; then if [ "$SCRIPT_MANAGED_SKIP_VERIFY" = "true" ]; then SCRIPT_MANAGED_SKIP_VERIFY="false"; else SCRIPT_MANAGED_SKIP_VERIFY="true"; fi
    if _commit_service_config_and_restart; then local new_stat="关闭"; if [ "$SCRIPT_MANAGED_SKIP_VERIFY" = "true" ]; then new_stat="开启"; fi; echo "跳过TLS验证更新为: $new_stat"; else echo "错误:更新失败。"; fi; else echo "操作取消。"; fi
}

handle_set_custom_dns() {
  _read_and_set_current_service_config_vars; echo "--- 配置自定义DNS (-dns) ---"; echo "当前: ${SCRIPT_MANAGED_CUSTOM_DNS:-未设置}"
  read -r -p "新DNS服务器 (例如8.8.8.8, 或 'clear', 回车不改): " dns_input
  if [ -n "$dns_input" ]; then if [ "$dns_input" == "clear" ]; then SCRIPT_MANAGED_CUSTOM_DNS=""; if _commit_service_config_and_restart; then echo "自定义DNS已清除。"; else echo "错误:清除失败。"; fi
  else if [[ "$dns_input" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ || "$dns_input" =~ ^[a-zA-Z0-9.-]+(:[0-9]+)?$ ]]; then SCRIPT_MANAGED_CUSTOM_DNS="$dns_input"; if _commit_service_config_and_restart; then echo "自定义DNS更新为 ${SCRIPT_MANAGED_CUSTOM_DNS}。"; else echo "错误:更新失败。"; fi; else echo "错误:无效DNS格式。"; fi; fi; else echo "未修改。"; fi
}

handle_reset_web_password() {
  echo "--- 重置Web界面密码 ---"; if ! [ -f "$DDNS_GO_INSTALL_PATH/ddns-go" ]; then echo "错误: ddns-go程序未找到。"; return 1; fi
  if [ ! -f "$DDNS_GO_CONFIG_FILE" ]; then echo "错误: YAML配置文件 $DDNS_GO_CONFIG_FILE 未找到。"; return 1; fi
  local new_pwd new_pwd_confirm; read -r -s -p "新密码: " new_pwd; echo ""; if [ -z "$new_pwd" ]; then echo "密码不能为空。取消。"; return; fi
  read -r -s -p "确认新密码: " new_pwd_confirm; echo ""; if [ "$new_pwd" != "$new_pwd_confirm" ]; then echo "密码不一致。取消。"; return; fi
  echo "准备重置..."; local svc_active=false; if systemctl is-active --quiet ddns-go; then svc_active=true; echo "  临时停止服务..."; if ! systemctl stop ddns-go; then echo "错误:停止服务失败。"; return 1; fi; sleep 1; fi
  echo "  执行重置命令..."; if [ ! -x "$DDNS_GO_INSTALL_PATH/ddns-go" ]; then chmod +x "$DDNS_GO_INSTALL_PATH/ddns-go"; fi
  "$DDNS_GO_INSTALL_PATH/ddns-go" -resetPassword "$new_pwd" -c "$DDNS_GO_CONFIG_FILE"; local reset_stat=$?
  if [ "$svc_active" = "true" ]; then echo "  重启服务..."; systemctl start ddns-go; fi
  if [ $reset_stat -eq 0 ]; then echo "密码重置指令已发送。新密码 '$new_pwd'。请尝试登录确认。"; else echo "错误:密码重置失败(码: $reset_stat)。检查日志。"; if [ "$svc_active" = true ] && ! systemctl is-active --quiet ddns-go; then echo "警告:服务重置后未自动重启。" >&2; fi; fi
}

customize_auto_update_schedule() {
  echo "--- 配置程序自动更新计划 ---"; if ! is_ddns_go_installed || ! [ -f "$UPDATE_SCRIPT_PATH" ]; then echo "ddns-go或更新脚本未安装。"; return; fi
  CURRENT_CRON_JOB=$(crontab -l 2>/dev/null | grep -F "$UPDATE_SCRIPT_PATH" | grep -F "$CRON_COMMENT_TAG"); CRON_MINUTE=$DEFAULT_UPDATE_MINUTE; CRON_HOUR=$DEFAULT_UPDATE_HOUR; CRON_DOW=$DEFAULT_UPDATE_DAY_OF_WEEK 
  if [ -n "$CURRENT_CRON_JOB" ]; then CRON_MINUTE=$(echo "$CURRENT_CRON_JOB"|awk '{print $1}'); CRON_HOUR=$(echo "$CURRENT_CRON_JOB"|awk '{print $2}'); CRON_DOW=$(echo "$CURRENT_CRON_JOB"|awk '{print $5}');fi 
  local sched_desc="每天 ${CRON_HOUR}:${CRON_MINUTE}"; if [ "$CRON_DOW" != "*" ]; then local days=("日" "一" "二" "三" "四" "五" "六"); if [[ "$CRON_DOW" =~ ^[0-6]$ ]]; then sched_desc="每周${days[$CRON_DOW]} ${CRON_HOUR}:${CRON_MINUTE}"; fi; fi; echo "当前计划: $sched_desc"
  local freq_c new_dow_in new_hr_in new_min_in new_dow="*" new_hr="$CRON_HOUR" new_min="$CRON_MINUTE"
  local def_freq="1"; if [ "$CRON_DOW" != "*" ] && [[ "$CRON_DOW" =~ ^[0-6]$ ]]; then def_freq="2"; fi
  read -r -p "更新周期 [1]每日 [2]每周 (默认: $def_freq): " freq_c; freq_c=${freq_c:-$def_freq}
  if [ "$freq_c" == "2" ]; then local def_dow="3"; if [[ "$CRON_DOW" =~ ^[0-6]$ ]]; then def_dow="$CRON_DOW"; fi; read -r -p "星期几(0日..6六,默认:$def_dow): " new_dow_in; new_dow=${new_dow_in:-$def_dow}; if ! [[ "$new_dow" =~ ^[0-6]$ ]]; then echo "无效星期,用周三"; new_dow="3"; fi; else new_dow="*"; fi
  read -r -p "小时(0-23,默认:$CRON_HOUR): " new_hr_in; new_hr=${new_hr_in:-$CRON_HOUR}
  read -r -p "分钟(0-59,默认:$CRON_MINUTE): " new_min_in; new_min=${new_min_in:-$CRON_MINUTE}
  if ! [[ "$new_hr" =~ ^([0-9]|1[0-9]|2[0-3])$ && "$new_min" =~ ^([0-9]|[1-5][0-9])$ ]]; then echo "错误:时间无效。"; return; fi
  local new_sched_desc="每天 ${new_hr}:${new_min}"; if [ "$new_dow" != "*" ]; then local days_n=("日" "一" "二" "三" "四" "五" "六"); new_sched_desc="每周${days_n[$new_dow]} ${new_hr}:${new_min}"; fi
  echo "配置新计划为: $new_sched_desc..."; if _configure_auto_update_cronjob "$new_min" "$new_hr" "$new_dow"; then echo "计划已修改。"; else echo "错误:修改失败。"; fi
}

handle_run_program_update_now() {
  echo "--- 立即更新ddns-go程序 ---"
  if ! is_ddns_go_installed; then echo "ddns-go 未安装。"; return; fi
  if [ ! -f "$UPDATE_SCRIPT_PATH" ]; then
    echo "错误: 更新脚本 $UPDATE_SCRIPT_PATH 未找到。请先配置自动更新或重新安装。"
    return
  fi
  if [ ! -x "$UPDATE_SCRIPT_PATH" ]; then
    echo "错误: 更新脚本 $UPDATE_SCRIPT_PATH 不可执行。尝试修复..."
    chmod +x "$UPDATE_SCRIPT_PATH"
    if [ ! -x "$UPDATE_SCRIPT_PATH" ]; then
        echo "修复失败。请检查文件权限。"
        return
    fi
  fi

  local update_output
  local update_status

  echo "执行更新脚本: $UPDATE_SCRIPT_PATH" 
  echo "更新日志将记录到: $DDNS_GO_UPDATE_LOG_FILE"
  
  update_output=$(bash "$UPDATE_SCRIPT_PATH" 2>&1) 
  update_status=$?

  echo "" 
  echo "更新脚本输出:" 
  if [ -n "$update_output" ]; then 
    printf "%s\n" "$update_output" 
  else 
    if [ $update_status -eq 0 ]; then
        echo "(更新脚本执行成功,但无直接控制台输出。请检查日志。)"
    else
        echo "(更新脚本无标准输出/错误输出,且执行失败)"
    fi
  fi
  echo "------------------------" 
 
  if [ $update_status -eq 0 ]; then
    if [ -z "$update_output" ]; then 
        echo "提示: 更新脚本执行完毕 (退出码0),无明确状态输出。请检查日志。"
    fi
  else 
    echo "错误: 更新脚本执行时遇到问题 (退出状态码: $update_status)。请检查以上输出和日志。"
  fi

  echo "详细日志亦可查阅: tail -n 30 $DDNS_GO_UPDATE_LOG_FILE"
  echo "" 
}

handle_toggle_web_service() { 
  _read_and_set_current_service_config_vars; echo "--- 管理Web服务 ---"
  local choice; if [ "$SCRIPT_MANAGED_WEB_ENABLED" = "true" ]; then echo "当前Web服务已启用 (端口: $SCRIPT_MANAGED_WEB_PORT)。"; read -r -p "要禁用吗 (加-noweb)? (y/N): " choice
    if [[ "$choice" =~ ^[Yy]$ ]]; then SCRIPT_MANAGED_WEB_ENABLED="false"; if _commit_service_config_and_restart; then echo "Web服务已禁用。"; else echo "错误:禁用失败。"; fi; else echo "操作取消。"; fi
  else echo "当前Web服务已禁用 (-noweb)。"; read -r -p "要启用吗 (端口 $SCRIPT_MANAGED_WEB_PORT)? (y/N): " choice
    if [[ "$choice" =~ ^[Yy]$ ]]; then SCRIPT_MANAGED_WEB_ENABLED="true"; if ! [[ "$SCRIPT_MANAGED_WEB_PORT" =~ ^[0-9]+$ && "$SCRIPT_MANAGED_WEB_PORT" -ge 1 && "$SCRIPT_MANAGED_WEB_PORT" -le 65535 ]]; then SCRIPT_MANAGED_WEB_PORT="$DEFAULT_WEB_PORT"; echo "警告:端口重置为默认$DEFAULT_WEB_PORT。"; fi
      if _commit_service_config_and_restart; then echo "Web服务已启用 (端口 $SCRIPT_MANAGED_WEB_PORT)。"; else echo "错误:启用失败。"; fi; else echo "操作取消。"; fi; fi; echo ""; 
}

handle_change_web_port() { 
  _read_and_set_current_service_config_vars; echo "--- 更改Web服务端口 ---"
  if [ "$SCRIPT_MANAGED_WEB_ENABLED" = "false" ]; then echo "警告:Web服务禁用中。新端口将在启用后生效。当前配置端口(若启用): $SCRIPT_MANAGED_WEB_PORT"; else echo "当前Web端口: $SCRIPT_MANAGED_WEB_PORT"; fi
  local new_port_in; read -r -p "新端口 (1-65535, 回车取消): " new_port_in; if [ -z "$new_port_in" ]; then echo "操作取消。"; return; fi
  if ! [[ "$new_port_in" =~ ^[0-9]+$ && "$new_port_in" -ge 1 && "$new_port_in" -le 65535 ]]; then echo "错误:无效端口。取消。"; return; fi
  SCRIPT_MANAGED_WEB_PORT="$new_port_in"; echo "配置Web端口为 $SCRIPT_MANAGED_WEB_PORT..."
  if _commit_service_config_and_restart; then echo "端口已配置。"; else echo "错误:配置失败。"; fi; echo ""; 
}

main_menu() {
  init_arch 
  _read_and_set_current_service_config_vars 

  clear
  echo "======================================"
  echo "    ddns-go 管理脚本 (v2.2.1)"
  echo "======================================"
  echo " ddns-go 当前启动配置 (来自服务文件):" 

  local web_status_text_param="已启用"
  if [ "$SCRIPT_MANAGED_WEB_ENABLED" = "false" ]; then web_status_text_param="已禁用 (-noweb)"; fi
  echo "   Web 服务状态: $web_status_text_param"

  local display_port_param="$SCRIPT_MANAGED_WEB_PORT"
  if [ "$SCRIPT_MANAGED_WEB_ENABLED" = "false" ]; then display_port_param="N/A"; fi
  echo "   Web 监听端口 (-l): $display_port_param"

  echo "   同步间隔 (-f): ${SCRIPT_MANAGED_SYNC_INTERVAL} 秒"
  echo "   比对频率 (-cacheTimes): ${SCRIPT_MANAGED_CACHE_TIMES:-未设置}"
  echo "   跳过TLS验证 (-skipVerify): ${SCRIPT_MANAGED_SKIP_VERIFY}"
  echo "   自定义DNS (-dns): ${SCRIPT_MANAGED_CUSTOM_DNS:-未设置}"
  echo "--------------------------------------" 
  echo "请选择操作:"
  echo "  1. 安装 ddns-go"
  echo "  2. 重新安装 ddns-go" 
  echo "  3. 彻底卸载 ddns-go"
  echo "  4. 检查 ddns-go 状态"
  
  local opt_num=5
  local web_service_option_text="管理 ddns-go Web 服务" 
  if [ -f "$DDNS_GO_SERVICE_FILE" ]; then 
    if [ "$SCRIPT_MANAGED_WEB_ENABLED" = "true" ]; then web_service_option_text="禁用 ddns-go Web 服务 (当前服务配置: 开)";
    else web_service_option_text="启用 ddns-go Web 服务 (当前服务配置: 关)"; fi
  fi

  echo "  $opt_num. $web_service_option_text"; local toggle_web_opt_num=$opt_num; ((opt_num++))
  echo "  $opt_num. 更改 ddns-go Web 服务端口"; local change_port_opt_num=$opt_num; ((opt_num++))
  echo "  $opt_num. 配置同步间隔 (-f)"; local set_f_opt_num=$opt_num; ((opt_num++))
  echo "  $opt_num. 配置服务商比对频率 (-cacheTimes)"; local set_ct_opt_num=$opt_num; ((opt_num++))
  echo "  $opt_num. 切换TLS证书验证 (-skipVerify)"; local toggle_sv_opt_num=$opt_num; ((opt_num++))
  echo "  $opt_num. 配置自定义DNS服务器 (-dns)"; local set_dns_opt_num=$opt_num; ((opt_num++))
  echo "  $opt_num. 重置Web界面密码"; local reset_pwd_opt_num=$opt_num; ((opt_num++)) 
  echo "  $opt_num. 配置ddns-go程序自动更新计划"; local config_autoupdate_opt_num=$opt_num; ((opt_num++))
  echo "  $opt_num. 立即更新ddns-go程序"; local run_update_now_opt_num=$opt_num; ((opt_num++))
  echo "  $opt_num. 退出脚本"; local exit_opt_num=$opt_num
  echo "--------------------------------------"
  read -r -p "请输入选项数字 [1-$exit_opt_num]: " choice

  case "$choice" in
    1) handle_install ;; 2) handle_reinstall ;; 3) handle_uninstall ;; 4) handle_status ;;
    "$toggle_web_opt_num") handle_toggle_web_service ;; "$change_port_opt_num") handle_change_web_port ;;
    "$set_f_opt_num") handle_set_sync_interval ;; "$set_ct_opt_num") handle_set_cache_times ;;
    "$toggle_sv_opt_num") handle_toggle_skip_verify ;; "$set_dns_opt_num") handle_set_custom_dns ;;
    "$reset_pwd_opt_num") handle_reset_web_password ;; "$config_autoupdate_opt_num") customize_auto_update_schedule ;;
    "$run_update_now_opt_num") handle_run_program_update_now ;; "$exit_opt_num") echo "正在退出脚本..."; exit 0 ;;
    *) echo "无效选项 '$choice',请输入有效数字。" ;;
  esac
  
  if [[ "$choice" -ne "$exit_opt_num" ]]; then press_enter_to_continue; fi
}

check_root
while true; do main_menu; done


DDNS-GO管理脚本
作者
Administrator
发表于
2025-10-11
License
CC BY-NC-SA 4.0

评论